未命名
简介
什么是 Vim?
Vim 是一个历史悠久的文本编辑器,可以追溯到
qed。
Bram Moolenaar 于
1991 年发布初始版本。
Linux、Mac 用户,可以使用包管理器安装 Vim,对于 Windows 用户,可以从我的网盘 下载。该版本可轻易添加 python 、python3 、lua 等支持,只需要安装 python、lua
即可。
项目在 Github 上开发,项目讨论请订阅
vim_dev 邮件列表。
通过阅读 Why, oh WHY, do those #?@! nutheads use vi?
来对 Vim 进行大致的了解。
Vim 哲学
Vim 采用模式编辑的理念,即它提供了多种模式,按键在不同的模式下作用不同。你可以在普通模式 下浏览文件,在插入模式下插入文本,在可视模式下选择行,在命令模式下执行命令等等。起初这听起来可能很复杂,但是这有一个很大的优点:不需要通过同时按住多个键来完成操作,大多数时候你只需要依次按下这些按键即可。越常用的操作,所需要的按键数量越少。
和模式编辑紧密相连的概念是 操作符 和 动作。操作符 指的是开始某个行为,例如:修改、删除或者选择文本,之后你要用一个 动作 来指定需要操作的文本区域。比如,要改变括号内的文本,需要执行 ci( (读做 change inner parentheses);删除整个段落的内容,需要执行 dap (读做:delete around paragraph)。
如果你能看见 Vim 老司机操作,你会发现他们使用 Vim 脚本语言就如同钢琴师弹钢琴一样。复杂的操作只需要几个按键就能完成。他们甚至不用刻意去想,因为这已经成为肌肉记忆了。这减少认识负荷并帮助人们专注于实际任务。
入门
Vim 自带一个交互式的教程,内含你需要了解的最基础的信息,你可以通过终端运行以下命令打开教程:
1 | $ vimtutor |
不要因为这个看上去很无聊而跳过,按照此教程多练习。你以前用的 IDE 或者其他编辑器很少是有“模式”概念的,因此一开始你会很难适应模式切换。但是你 Vim 使用的越多,肌肉记忆 将越容易形成。
Vim 基于一个 vi 克隆,叫做 Stevie,支持两种运行模式:“compatible” 和 “nocompatible”。在兼容模式下运行 Vim 意味着使用 vi 的默认设置,而不是 Vim 的默认设置。除非你新建一个用户的 vimrc 或者使用 vim -N 命令启动 Vim,否则就是在兼容模式下运行 Vim!请大家不要在兼容模式下运行 Vim。
下一步
-
创建你自己的 vimrc。
-
在第一周准备备忘录。
-
通读基础章节了解 Vim 还有哪些功能。
-
按需学习!Vim 是学不完的。如果你遇到了问题,先上网寻找解决方案,你的问题可能已经被解决了。Vim 拥有大量的参考文档,知道如何利用这些参考文档很有必要:获取离线帮助。
-
浏览附加资源。
最后一个建议:使用插件之前,请先掌握 Vim 的基本操作。很多插件都只是对 Vim 自带功能的封装。
返回主目录 ⤴️
精简的 vimrc
Vim 启动是会按照一定的优先顺序来搜索配置文件,这个顺序,可以通过 :version 命令查看。下面分 Windows 系统,和 *niux 系统分别来说明 Vim 是如何载入配置文件的。
Windows 系统
1 | system vimrc file: "$VIM\vimrc" |
我们只看上面这一段,Vim 会优先读取 user vimrc file: $HOME\_vimrc, 当这一文件不存在是,
Vim 再去寻找 2nd user vimrc file: $HOME\vimfiles\vimrc; 倘若这个文件还是不存在,那么 Vim
会去继续寻找 3rd user vimrc file: $VIM\_vimrc。 了解以上顺序后,就不会再因为 Vim
总是不读取配置文件而感到烦恼了。
Linux 或者 Mac OS
同 Windows 系统类似,也可以使用 :version 命令查看 vim 载入配置的优先顺序。
1 | 系统 vimrc 文件: "/etc/vimrc" |
你可以在网上找到许多精简的 vimrc 配置文件,我的版本可能并不是最简单的版本,但是我的版本提供了一套我认为良好的,非常适合入门的设置。
最终你需要阅读完那些设置,然后自行决定需要使用哪些。😃
精简的 vimrc 地址:minimal-vimrc
如果你有兴趣,这里是我(原作者)的 vimrc。
建议:大多数插件作者都维护不止一个插件并且将他们的 vimrc 放在 Github 上展示(通常放在叫做 “vim-config” 或者 “dotfiles” 的仓库中),所以当你发现你喜欢的插件时,去插件维护者的 Github 主页看看有没有这样的仓库。
返回主目录 ⤴️
我正在使用什么样的 Vim
使用 :version 命令将向你展示当前正在运行的 Vim 的所有相关信息,包括它是如何编译的。
第一行告诉你这个二进制文件的编译时间和版本号,比如:7.4。接下来的一行呈现 Included patches: 1-1051,这是补丁版本包。因此你 Vim 确切的版本号是 7.4.1051。
另一行显示着一些像 Tiny version without GUI 或者 Huge version with GUI 的信息。很显然这些信息告诉你当前的 Vim 是否支持 GUI,例如:从终端中运行 gvim 或者从终端模拟器中的 Vim 内运行 :gui 命令。另一个重要的信息是 Tiny 和 Huge。Vim 的特性集区分被叫做 tiny,small,normal,big and huge,所有的都实现不同的功能子集。
:version 主要的输出内容是特性列表。+clipboard 意味这剪贴板功能被编译支持了,-clipboard 意味着剪贴板特性没有被编译支持。
一些功能特性需要编译支持才能正常工作。例如:为了让 :prof 工作,你需要使用 huge 模式编译的 Vim,因为那种模式启用了 +profile 特性。
如果你的输出情况并不是那样,并且你是从包管理器安装 Vim 的,确保你安装了 vim-x,vim-x11,vim-gtk,vim-gnome 这些包或者相似的,因为这些包通常都是 huge 模式编译的。
你也可以运行下面这段代码来测试 Vim 版本以及功能支持:
1 | " Do something if running at least Vim 7.4.42 with +profile enabled. |
相关帮助:
1 | :h :version |
返回主目录 ⤴️
备忘录
为了避免版权问题,我只贴出链接:
-
[[Vim 备忘录]]
-
https://cdn.shopify.com/s/files/1/0165/4168/files/preview.png
-
http://michael.peopleofhonoronly.com/vim/vim_cheat_sheet_for_programmers_screen.png
-
http://www.rosipov.com/images/posts/vim-movement-commands-cheatsheet.png
或者在 Vim 中快速打开备忘录:vim-cheat40。
返回主目录 ⤴️
基础
缓冲区,窗口,标签
Vim 是一个文本编辑器。每次文本都是作为缓冲区的一部分显示的。每一份文件都是在他们自己独有的缓冲区打开的,插件显示的内容也在它们自己的缓冲区中。
缓冲区有很多属性,比如这个缓冲区的内容是否可以修改,或者这个缓冲区是否和文件相关联,是否需要同步保存到磁盘上。
窗口 是缓冲区上一层的视窗。如果你想同时查看几个文件或者查看同一文件的不同位置,那样你会需要窗口。
请别把他们叫做 分屏 。你可以把一个窗口分割成两个,但是这并没有让这两个窗口完全 分离 。
窗口可以水平或者竖直分割并且现有窗口的高度和宽度都是可以被调节设置的,因此,如果你需要多种窗口布局,请考虑使用标签。
标签页 (标签)是窗口的集合。因此当你想使用多种窗口布局时候请使用标签。
简单的说,如果你启动 Vim 的时候没有附带任何参数,你会得到一个包含着一个呈现一个缓冲区的窗口的标签。
顺带提一下,缓冲区列表是全局可见的,你可以在任何标签中访问任何一个缓冲区。
返回主目录 ⤴️
已激活、已载入、已列出、已命名的缓冲区
用类似 vim file1 的命令启动 Vim 。这个文件的内容将会被加载到缓冲区中,你现在有一个已载入的缓冲区。如果你在 Vim 中保存这个文件,缓冲区内容将会被同步到磁盘上(写回文件中)。
由于这个缓冲区也在一个窗口上显示,所以他也是一个已激活的缓冲区。如果你现在通过 :e file2 命令加载另一个文件,file1 将会变成一个隐藏的缓冲区,并且 file2 变成已激活缓冲区。
使用 :ls 我们能够列出所有可以列出的缓冲区。插件缓冲区和帮助缓冲区通常被标记为不可以列出的缓冲区,因为那并不是你经常需要在编辑器中编辑的常规文件。通过 :ls! 命令可以显示被放入缓冲区列表的和未被放入列表的缓冲区。
未命名的缓冲区是一种没有关联特定文件的缓冲区,这种缓冲区经常被插件使用。比如 :enew 将会创建一个无名临时缓冲区。添加一些文本然后使用 :w /tmp/foo 将他写入到磁盘,这样这个缓冲区就会变成一个已命名的缓冲区。
返回主目录 ⤴️
参数列表
全局缓冲区列表是 Vim 的特性。在这之前的 vi 中,仅仅只有参数列表,参数列表在 Vim 中依旧可以使用。
每一个通过 shell 命令传递给 Vim 的文件名都被记录在一个参数列表中。可以有多个参数列表:默认情况下所有参数都被放在全局参数列表下,但是你可以使用 :arglocal 命令去创建一个新的本地窗口的参数列表。
使用 :args 命令可以列出当前参数。使用 :next,:previous,:first,:last 命令可以在切换在参数列表中的文件。通过使用 :argadd,:argdelete 或者 :args 等命令加上一个文件列表可以改变参数列表。
偏爱缓冲区列表还是参数列表完全是个人选择,我的印象中大多数人都是使用缓冲区列表的。
然而参数列表在有些情况下被大量使用:批处理使用 :argdo! 一个简单的重构例子:
1 | :args **/*.[ch] |
这条命令将替换掉当前目录下以及当前目录的子目录中所有的 C 源文件和头文件中的“foo”,并用“bar”代替。
相关帮助::h argument-list
返回主目录 ⤴️
按键映射
使用 :map 命令家族你可以定义属于你自己的快捷键。该家族的每一个命令都限定在特定的模式下。从技术上来说 Vim 自带高达 12 中模式,其中 6 种可以被映射。另外一些命令作用于多种模式:
| 递归 | 非递归 | 模式 |
|---|---|---|
:map |
:noremap |
normal, visual, operator-pending |
:nmap |
:nnoremap |
normal |
:xmap |
:xnoremap |
visual |
:cmap |
:cnoremap |
command-line |
:omap |
:onoremap |
operator-pending |
:imap |
:inoremap |
insert |
例如:这个自定义的快捷键只在普通模式下工作。
1 | :nmap <space> :echo "foo"<cr> |
使用 :nunmap <space> 可以取消这个映射。
对于更少数,不常见的模式(或者他们的组合),查看 :h map-modes。
到现在为止还好,对新手而言有一个问题会困扰他们::nmap 是递归执行的!结果是,右边执行可能的映射。
你自定义了一个简单的映射去输出“Foo”:
1 | :nmap b :echo "Foo"<cr> |
但是如果你想要映射 b (回退一个单词)的默认功能到一个键上呢?
1 | :nmap a b |
如果你敲击a,我们期望着光标回退到上一个单词,但是实际情况是“Foo”被输出到命令行里!因为在右边,b 已经被映射到别的行为上了,换句话说就是 :echo "Foo"<cr>。
解决此问题的正确方法是使用一种 非递归 的映射代替:
1 | :nnoremap a b |
经验法则:除递归映射是必须的,否则总是使用非递归映射。
通过不给一个右值来检查你的映射。比如:nmap 显示所以普通模式下的映射,:nmap <leader> 显示所有以 <leader> 键开头的普通模式下的映射。
如果你想禁止用标准映射,把他们映射到特殊字符 <nop> 上,例如::noremap <left> <nop>。
相关帮助:
1 | :h key-notation |
返回主目录 ⤴️
映射前置键
映射前置键(Leader 键)本身就是一个按键映射,默认为 \。我们可以通过在 map 中调用 <leader> 来为把它添加到其他按键映射中。
1 | nnoremap <leader>h :helpgrep<space> |
这样,我们只需要先按 \ 然后按 h 就可以激活这个映射 :helpgrep<space>。如果你想通过先按 空格 键来触发,只需要这样做:
1 | let g:mapleader = ' ' |
此处建议使用 g:mapleader,因为在 Vim 脚本中,函数外的变量缺省的作用域是全局变量,但是在函数内缺省作用域是局部变量,而设置快捷键前缀需要修改全局变量 g:mapleader 的值。
另外,还有一个叫 <localleader> 的,可以把它理解为局部环境中的 <leader>,默认值依然为 \。当我们需要只对某一个条件下(比如,特定文件类型的插件)的缓冲区设置特别的 <leader> 键,那么我们就可以通过修改当前环境下的 <localleader> 来实现。
注意:如果你打算设置 Leader 键,请确保在设置按键映射之前,先设置好 Leader 键。如果你先设置了含有 Leader 键的映射,然后又修改了 Leader 键,那么之前映射内的 Leader 键是不会因此而改变的。你可以通过执行 :nmap <leader> 来查看普通模式中已绑定给 Leader 键的所有映射。
请参阅 :h mapleader 与 :h maploacalleader 来获取更多帮助。
返回主目录 ⤴️
寄存器
寄存器就是存储文本的地方。我们常用的「复制」操作就是把文本存储到寄存器,「 粘贴」 操作就是把文本从寄存器中读出来。顺便,在 Vim 中复制的快捷键是 y,粘贴的快捷键是 p。
Vim 为我们提供了如下的寄存器:
| 类型 | 标识 | 读写者 | 是否为只读 | 包含的字符来源 |
|---|---|---|---|---|
| Unnamed | " |
vim | 否 | 最近一次的复制或删除操作 (d, c, s, x, y) |
| Numbered | 0至9 |
vim | 否 | 寄存器 0: 最近一次复制。寄存器 1: 最近一次删除。寄存器 2: 倒数第二次删除,以此类推。对于寄存器 1 至 9,他们其实是只读的最多包含 9 个元素的队列。这里的队列即为数据类型 queue |
| Small delete | - |
vim | 否 | 最近一次行内删除 |
| Named | a至z, A至Z |
用户 | 否 | 如果你通过复制操作存储文本至寄存器 a,那么 a 中的文本就会被完全覆盖。如果你存储至 A,那么会将文本添加给寄存器 a,不会覆盖之前已有的文本 |
| Read-only | :与.和% |
vim | 是 | :: 最近一次使用的命令,.: 最近一次添加的文本,%: 当前的文件名 |
| Alternate buffer | # |
vim | 否 | 大部分情况下,这个寄存器是当前窗口中,上一次访问的缓冲区。请参阅 :h alternate-file 来获取更多帮助 |
| Expression | = |
用户 | 否 | 复制 VimL 代码时,这个寄存器用于存储代码片段的执行结果。比如,在插入模式下复制 <c-r>=5+5<cr>,那么这个寄存器就会存入 10 |
| Selection | +和* |
vim | 否 | * 和 + 是 剪贴板 寄存器 |
| Drop | ~ |
vim | 是 | 最后一次拖拽添加至 Vim 的文本(需要 “+dnd” 支持,暂时只支持 GTK GUI。请参阅 :help dnd 及 :help quote~) |
| Black hole | _ |
vim | 否 | 一般称为黑洞寄存器。对于当前操作,如果你不希望在其他寄存器中保留文本,那就在命令前加上 _。比如,"_dd 命令不会将文本放到寄存器 "、1、+ 或 * 中 |
| Last search pattern | / |
vim | 否 | 最近一次通过 /、? 或 :global 等命令调用的匹配条件 |
只要不是只读的寄存器,用户都有权限修改它的内容,比如:
1 | :let @/ = 'register' |
这样,我们按 n 的时候就会跳转到单词"register" 出现的地方。
有些时候,你的操作可能已经修改了寄存器,而你没有察觉到。请参阅 :h registers 获取更多帮助。
上面提到过,复制的命令是 y,粘贴的命令是 p 或者 P。但请注意,Vim 会区分「字符选取」与「行选取」。请参阅 :h linewise 获取更多帮助。
行选取:命令 yy 或 Y 都是复制当前行。这时移动光标至其他位置,按下 p 就可以在光标下方粘贴复制的行,按下 P 就可以在光标上方粘贴至复制的行。
字符选取:命令 0yw 可以复制第一个单词。这时移动光标至其他位置,按下 p 就可以在当前行、光标后的位置粘贴单词,按下 P 就可以在当前行、光标前的位置粘贴单词。
将文本存到指定的寄存器中:命令 "aY 可以将当前行复制,并存储到寄存器 a 中。这时移动光标至其他位置,通过命令 "AY 就可以把这一行的内容扩展到寄存器 a 中,而之前存储的内容也不会丢失。
为了便于理解和记忆,建议大家现在就试一试上面提到的这些操作。操作过程中,你可以随时通过 :reg 来查看寄存器的变化。
有趣的是:在 Vim 中,y 是复制命令,源于单词 “yanking”。而在 Emacs 中,“yanking” 代表的是粘贴(或者说,重新插入刚才删掉的内容),而并不是复制。
返回主目录 ⤴️
范围
范围 (Ranges) 其实很好理解,但很多 Vim 用户的理解不到位。
-
很多命令都可以加一个数字,用于指明操作范围
-
范围可以是一个行号,用于指定某一行
-
范围也可以是一对通过
,或;分割的行号 -
大部分命令,默认只作用于当前行
-
只有
:write和:global是默认作用于所有行的
范围的使用是十分直观的。以下为一些例子(其中,:d 为 :delete 的缩写):
| 命令 | 操作的行 |
|---|---|
:d |
当前行 |
:.d |
当前行 |
:1d |
第一行 |
:$d |
最后一行 |
:1,$d |
所有行 |
:%d |
所有行(这是 1,$ 的语法糖) |
:.,5d |
当前行至第 5 行 |
:,5d |
同样是当前行至第 5 行 |
:,+3d |
当前行及接下来的 3 行 |
:1,+3d |
第一行至当前行再加 3 行 |
:,-3d |
当前行及向上的 3 行(Vim 会弹出提示信息,因为这是一个保留的范围) |
:3,'xdelete |
第三行至标注 为 x 的那一行 |
:/^foo/,$delete |
当前行以下,以字符 “foo” 开头的那一行至结尾 |
:/^foo/+1,$delete |
当前行以下,以字符 “foo” 开头的那一行的下一行至结尾 |
需要注意的是,; 也可以用于表示范围。区别在于,a,b 的 b 是以当前行作为参考的。而 a;b 的 b 是以 a 行作为参考的。举个例子,现在你的光标在第 5 行。这时 :1,+1d 会删除第 1 行至第 6 行,而 :1;+1d 会删除第 1 行和第 2 行。
如果你想设置多个寻找条件,只需要在条件前加上 /,比如:
1 | :/foo//bar//quux/d |
这就会删除当前行之后的某一行。定位方式是,先在当前行之后寻找第一个包含 “foo” 字符的那一行,然后在找到的这一行之后寻找第一个包含 “bar” 字符的那一行,然后再在找到的这一行之后寻找第一个包含 “quux” 的那一行。删除的就是最后找到的这一行。
有时,Vim 会在命令前自动添加范围。举个例子,如果你先通过 V 命令进入行选取模式,选中一些行后按下 : 进入命令模式,这时候你会发现 Vim 自动添加了 '<,'> 范围。这表示,接下来的命令会使用之前选取的行号作为范围。但如果后续命令不支持范围,Vim 就会报错。为了避免这样的情况发生,有些人会设置这样的按键映射::vnoremap foo :<c-u>command,组合键 Ctrl + u 可以清除当前命令行中的内容。
另一个例子是在普通模式中按下 !!,命令行中会出现 :.!。如果这时你如果输入一个外部命令,那么当前行的内容就会被这个外部命令的输出替换。你也可以通过命令 :?^$?+1,/^$/-1!ls 把当前段落的内容替换成外部命令 ls 的输出,原理是向前和向后各搜索一个空白行,删除这两个空白行之间的内容,并将外部命令 ls 的输出放到这两个空白行之间。
请参阅以下两个命令来获取更多帮助:
1 | :h cmdline-ranges |
返回主目录 ⤴️
标注
你可以使用标注功能来标记一个位置,也就是记录文件某行的某个位置。
| 标注 | 设置者 | 使用 |
|---|---|---|
a-z |
用户 | 仅对当前的一个文件生效,也就意味着只可以在当前文件中跳转 |
A-Z |
用户 | 全局标注,可以作用于不同文件。大写标注也称为「文件标注」。跳转时有可能会切换到另一个缓冲区 |
0-9 |
viminfo | 0 代表 viminfo 最后一次被写入的位置。实际使用中,就代表 Vim 进程最后一次结束的位置。1 代表 Vim 进程倒数第二次结束的位置,以此类推 |
如果想跳转到指定的标注,你可以先按下 ' / g' 或者 ` / g` 然后按下标注名。
如果你想定义当前文件中的标注,可以先按下 m 再按下标注名。比如,按下 mm 就可以把当前位置标注为 m。在这之后,如果你的光标切换到了文件的其他位置,只需要通过 'm 或者 `m即可回到刚才标注的行。区别在于,'m会跳转回被标记行的第一个非空字符,而`m会跳转回被标记行的被标记列。根据 viminfo 的设置,你可以在退出 Vim 的时候保留小写字符标注。请参阅:h viminfo-' 来获取更多帮助。
如果你想定义全局的标注,可以先按下 m 再按下大写英文字符。比如,按下 mM 就可以把当前文件的当前位置标注为 M。在这之后,就算你切换到其他的缓冲区,依然可以通过 'M 或 `M 跳转回来。
关于跳转,还有以下的方式:
| 按键 | 跳转至 |
|---|---|
'[ 与 `[ |
上一次修改或复制的第一行或第一个字符 |
'] 与 `] |
上一次修改或复制的最后一行或最后一个字符 |
'< 与 `< |
上一次在可视模式下选取的第一行或第一个字符 |
'> 与 `> |
上一次在可视模式下选取的最后一行或最后一个字符 |
'' 与 `' |
上一次跳转之前的光标位置 |
'" 与 `" |
上一次关闭当前缓冲区时的光标位置 |
'^ 与 `^ |
上一次插入字符后的光标位置 |
'. 与 `. |
上一次修改文本后的光标位置 |
'( 与 `( |
当前句子的开头 |
') 与 `) |
当前句子的结尾 |
'{ 与 `{ |
当前段落的开头 |
'} 与 `} |
当前段落的结尾 |
标注也可以搭配 范围 一起使用。前面提到过,如果你在可视模式下选取一些文本,然后按下 :,这时候你会发现命令行已经被填充了 :'<,'>。对照上面的表格,现在你应该明白了,这段代表的就是可视模式下选取的范围。
请使用 :marks 命令来显示所有的标注,参阅 :h mark-motions 来获取关于标注的更多帮助。
返回主目录 ⤴️
补全
Vim 在插入模式中为我们提供了多种补全方案。如果有多个补全结果,Vim 会弹出一个菜单供你选择。
常见的补全有标签、项目中引入的模块或库中的方法名、文件名、字典及当前缓冲区的字段。
针对不同的补全方案,Vim 为我们提供了不同的按键映射。这些映射都是在插入模式中通过 Ctrl + x 来触发:
| 映射 | 类型 | 帮助文档 |
|---|---|---|
<c-x><c-l> |
整行 | :h i^x^l |
<c-x><c-n> |
当前缓冲区中的关键字 | :h i^x^n |
<c-x><c-k> |
字典(请参阅 :h 'dictionary')中的关键字 |
:h i^x^k |
<c-x><c-t> |
同义词字典(请参阅 :h 'thesaurus')中的关键字 |
:h i^x^t |
<c-x><c-i> |
当前文件以及包含的文件中的关键字 | :h i^x^i |
<c-x><c-]> |
标签 | :h i^x^] |
<c-x><c-f> |
文件名 | :h i^x^f |
<c-x><c-d> |
定义或宏定义 | :h i^x^d |
<c-x><c-v> |
Vim 命令 | :h i^x^v |
<c-x><c-u> |
用户自定义补全(通过 'completefunc' 定义) |
:h i^x^u |
<c-x><c-o> |
Omni Completion(通过 'omnifunc' 定义) |
:h i^x^o |
<c-x>s |
拼写建议 | :h i^Xs |
尽管用户自定义补全与 Omni Completion 是不同的,但他们做的事情基本一致。共同点在于,他们都是一个监听当前光标位置的函数,返回值为一系列的补全建议。用户自定义补全是由用户定义的,基于用户的个人用途,因此你可以根据自己的喜好和需求随意定制。而 Omni Completion 是针对文件类型的补全,比如在 C 语言中补全一个结构体(struct)的成员(members),或者补全一个类的方法,因而它通常都是由文件类型插件设置和调用的。
如果你设置了 'complete' 选项,那么你就可以在一次操作中采用多种补全方案。这个选项默认包含了多种可能性,因此请按照自己的需求来配置。你可以通过 <c-n> 来调用下一个补全建议,或通过 <c-p> 来调用上一个补全建议。当然,这两个映射同样可以直接调用补全函数。请参阅 :h i^n 与 :h 'complete' 来获得更多帮助。
如果你想配置弹出菜单的行为,请一定要看一看 :h 'completeopt' 这篇帮助文档。默认的配置已经不错了,但我个人(原作者)更倾向于把 “noselect” 加上。
请参阅以下文档获取更多帮助:
1 | :h ins-completion |
返回主目录 ⤴️
动作,操作符,文本对象
动作也就是指移动光标的操作,你肯定很熟悉 h、j、k 和 l,以及 w 和 b。但其实,/ 也是一个动作。他们都可以搭配数字使用,比如 2?the<cr> 可以将光标移动到倒数第二个 “the” 出现的位置。
以下会列出一些常用的动作。你也可以通过 :h navigation 来获取更多的帮助。
操作符是对某个区域文本执行的操作。比如,d、~、gU 和 > 都是操作符。这些操作符既可以在普通模式下使用,也可以在可视模式下使用。在普通模式中,顺序是先按操作符,再按动作指令,比如 >j。在可视模式中,选中区域后直接按操作符就可以,比如 Vjd。
与动作一样,操作符也可以搭配数字使用,比如 2gUw 可以将当前单词以及下一个单词转成大写。由于动作和操作符都可以搭配数字使用,因此 2gU2w 与执行两次 gU2w 效果是相同的。
请参阅 :h operator 来查看所有的操作符。你也可以通过 :set tildeop 命令把 ~ 也变成一个操作符
值得注意的是,动作是单向的,而文本对象是双向的。文本对象不仅作用于符号(比如括号、中括号和大括号等)标记的范围内,也作用于整个单词、整个句子等其他情况。
文本对象不能用于普通模式中移动光标的操作,因为光标还没有智能到可以向两个方向同时跳转。但这个功能可以在可视模式中实现,因为在对象的一端选中的情况下,光标只需要跳转到另一端就可以了。
文本对象操作一般用 i 或 a 加上对象标识符操作,其中 i 表示在对象内(英文 inner)操作,a 表示对整个对象(英文 around)操作,这时开头和结尾的空格都会被考虑进来。举个例子,diw 可以删除当前单词,ci( 可以改变括号中的内容。
文本对象同样可以与数字搭配使用。比如,像 ((( ))) 这样的文本,假如光标位于最内层的括号上或最内层的括号内,那么 d2a( 将会删除从最内层开始的两对括号,以及他们之间的所有内容。其实,d2a( 这个操作等同于 2da(。在 Vim 的命令中,如果有两处都可以接收数字作为参数,那么最终结果就等同于两个数字相乘。在这里,d 与 a( 都是可以接收参数的,一个参数是 1,另一个是 2,我们可以把它们相乘然后放到最前面。
请参阅 :h text-objects 来获取更多关于文本对象的帮助。
返回主目录 ⤴️
自动命令
在特定的情况下,Vim 会传出事件。如果你想针对这些事件执行回调方法,那么就需要用到自动命令这个功能。
如果没有了自动命令,那你基本上是用不了 Vim 的。自动命令一直都在执行,只是很多时候你没有注意到。不信的话,可以执行命令 :au ,不要被结果吓到,这些是当前有效的所有自动命令。
请使用 :h {event} 来查看 Vim 中所有事件的列表,你也可以参考 :h autocmd-events-abc 来获取关于事件的更多帮助。
一个很常用的例子,就是针对文件类型执行某些设置:
1 | autocmd FileType ruby setlocal shiftwidth=2 softtabstop=2 comments-=:# |
但是缓冲区是如何知道当前的文件中包含 Ruby 代码呢?这其实是另一个自动命令检测的到的,然后把文件类型设置成为 Ruby,这样就触发了上面的 FileType 事件。
在配置 vimrc 的时候,一般第一行加进去的就是 filetype on。这就意味着,Vim 启动时会读取 filetype.vim 文件,然后根据文件类型来触发相应的自动命令。
如果你勇于尝试,可以查看下 :e $VIMRUNTIME/filetype.vim,然后在输出中搜索 “Ruby”。这样,你就会发现其实 Vim 只是通过文件扩展名 .rb 判断某个文件是不是 Ruby 的。
注意:对于相同事件,如果有多个自动命令,那么自动命令会按照定义时的顺序执行。通过 :au 就可以查看它们的执行顺序。
1 | au BufNewFile,BufRead *.rb,*.rbw setf ruby |
BufNewFile 与 BufRead 事件是被写在 Vim 源文件中的。因此,每当你通过 :e 或者类似的命令打开文件,这两个事件都会触发。然后,就是读取 filetype.vim 文件来判断打开的文件类型。
简单来说,事件和自动命令在 Vim 中的应用十分广泛。而且,Vim 为我们留出了一些易用的接口,方便用户配置适合自己的事件驱动回调。
请参阅 :h autocommand 来获取更多帮助
返回主目录 ⤴️
变更历史,跳转历史
在 Vim 中,用户最近 100 次的文字改动都会被保存在变更历史中。如果在同一行有多个小改动,那么 Vim 会把它们合并成一个。尽管内容改动会合并,但作用的位置还是会只记录下最后一次改动的位置。
在你移动光标或跳转的时候,每一次的移动或跳转前的位置会被记录到跳转历史中。类似地,跳转历史也可以最多保存 100 条记录。对于每个窗口,跳转记录是独立的。但当你分离窗口时(比如使用 :split 命令),跳转历史会被复制过去。
Vim 中的跳转命令,包括 '、`、G、/、?、n、N、%、(、)、[[、]]、{、}、:s、:tag、L、M、H 以及开始编辑一个新文件的命令。
| 列表 | 显示所有条目 | 跳转到上一个位置 | 跳转到下一个位置 |
|---|---|---|---|
| 跳转历史 | :jumps |
[count]<c-o> |
[count]<c-i> |
| 变更历史 | :changes |
[count]g; |
[count]g, |
如果你执行第二列的命令显示所有条目,这时 Vim 会用 > 标记来为你指示当前位置。通常这个标记位于 1 的下方,也就代表最后一次的位置。
如果你希望关闭 Vim 之后还保留这些条目,请参阅 :h viminfo-' 来获取更多帮助。
注意:上面提到过,最后一次跳转前的位置也会记录在标注中,也可以通过连按 `` 或 ‘’ 跳转到那个位置
请参阅以下两个命令来获取更多帮助:
1 | :h changelist |
返回主目录 ⤴️
内容变更历史记录
Vim 会记录文本改变之前的状态。因此,你可以使用「撤销」操作 u 来取消更改,也可以通过「重做」操作 Ctrl + r 来恢复更改。
值得注意的是,Vim 采用 tree 数据结构来存储内容变更的历史记录,而不是采用 queue。你的每次改动都会成为存储为树的节点。而且,除了第一次改动(根节点),之后的每次改动都可以找到一个对应的父节点。每一个节点都会记录改动的内容和时间。其中,「分支」代表从任一节点到根节点的路径。当你进行了撤销操作,然后又输入了新的内容,这时候就相当于创建了分支。这个原理和 git 中的 branch(分支)十分类似。
考虑以下这一系列按键操作:
1 | ifoo<esc> |
那么现在,Vim 中会显示三行文本,分别是 “foo”、“bar” 和 “quux”。这时候,存储的树形结构如下:
foo(1)
/
bar(2)
/ \
baz(3) quux(4)
这个树形结构共包含四次改动,括号中的数字就代表时间顺序。
现在,我们有两种方式遍历这个树结构。一种叫「按分支遍历」,一种叫「按时间遍历」。
撤销 u 与重做 Ctrl + r 操作是按分支遍历。对于上面的例子,现在我们有三行字符。这时候按 u 会回退到 “bar” 节点,如果再按一次 u 则会回退到 “foo” 节点。这时,如果我们按下 Ctrl + r 就会前进至 “bar” 节点,再按一次就回前进至 “quux” 节点。在这种方式下,我们无法访问到兄弟节点(即 “baz” 节点)。
与之对应的是按时间遍历,对应的按键是 g- 和 g+。对于上面的例子,按下 g- 会首先回退到 “baz” 节点。再次按下 g- 会回退到 “bar” 节点。
| 命令/按键 | 执行效果 |
|---|---|
[count]u 或 :undo [count] |
回退到 [count] 次改动之前 |
[count]<c-r> 或 :redo [count] |
重做 [count] 次改动 |
U |
回退至最新的改动 |
[count]g- 或 :earlier [count]? |
根据时间回退到 [count] 次改动之前。“?” 为 “s”、“m”、“h”、“d” 或 "f"之一。例如,:earlier 2d 会回退到两天之前。:earlier 1f 则会回退到最近一次文件保存时的内容 |
[count]g+ 或 :later [count]? |
类似 g-,但方向相反 |
内容变更记录会储存在内存中,当 Vim 退出时就会清空。如果需要持久化存储内容变更记录,请参阅备份文件,交换文件,撤销文件以及 viminfo 文件的处理章节的内容。
如果你觉得这一部分的内容难以理解,请参阅 undotree,这是一个可视化管理内容变更历史记录的插件。类似的还有 vim-mundo。
请参阅以下链接获取更多帮助:
1 | :h undo.txt |
返回主目录 ⤴️
全局位置信息表,局部位置信息表
在某一个动作返回一系列「位置」的时候,我们可以利用「全局位置信息表」和「局部位置信息表」来存储这些位置信息,方便以后跳转回对应的位置。每一个存储的位置包括文件名、行号和列号。
比如,编译代码是出现错误,这时候我们就可以把错误的位置直接显示在全局位置信息表,或者通过外部抓取工具使位置显示在局部位置信息表中。
尽管我们也可以把这些信息显示到一个空格缓冲区中,但用这两个信息表显示的好处在于接口调用很方便,而且也便于浏览输出。
Vim 中,全局位置信息表只能有一个,但每一个窗口都可以有自己的局部位置信息表。这两个信息表的外观看上去很类似,但在操作上会稍有不同。
以下为两者的操作比较:
| 动作 | 全局位置信息表 | 局部位置信息表 |
|---|---|---|
| 打开窗口 | :copen |
:lopen |
| 关闭窗口 | :cclose |
:lclose |
| 下一个条目 | :cnext |
:lnext |
| 上一个条目 | :cprevious |
:lprevious |
| 第一个条目 | :cfirst |
:lfirst |
| 最后一个条目 | :clast |
:llast |
请参阅 :h :cc 以及底下的内容,来获取更多命令的帮助。
应用实例:如果我们想用 grep 递归地在当前文件夹中寻找某个关键词,然后把输出结果放到全局位置信息表中,只需要这样:
1 | :let &grepprg = 'grep -Rn $* .' |
执行了上面的代码,你就能看到所有包含字符串 “foo” 的文件名以及匹配到的相关字段都会显示在全局位置信息表中。
返回主目录 ⤴️
宏
你可以在 Vim 中录制一系列按键,并把他们存储到寄存器中。对于一些需要临时使用多次的一系列操作,把它们作为宏保存起来会显著地提升效率。对于一些复杂的操作,建议使用 Vim 脚本来实现。
-
首先,按下 q,然后按下你想要保存的寄存器,任何小写字母都可以。比如我们来把它保存到
q这个寄存器中。按下qq,你会发现命令行里已经显示了 “recording @q”。 -
如果你已经录制完成,那么只需要再按一次 q 就可以结束录制。
-
如果你想调用刚才录制的宏,只需要
[count]@q -
如果你想调用上一次使用的宏,只需要
[count]@@
实例 1:
一个插入字符串 “abc” 后换行的宏,重复调用十次:
1 |
|
(对于上面这个功能,你同样可以通过如下的按键: oabc 然后 ESC 然后 10. 来实现)。
实例 2:
一个在每行前都加上行号的宏。从第一行开始,行号为 1,后面依次递增。我们可以通过 Ctrl + a 来实现递增的行号,在定义宏的时候,它会显示成 ^A。
1 |
|
这里能实现功能,是因为我们假定了文件最多只有 1000 行。但更好的方式是使用「递归」宏,它会一直执行,知道不能执行为止:
1 |
|
(对于上面这个插入行号的功能,如果你不愿意使用宏,同样可以通过这段按键操作来实现::%s/^/\=line('.') . '. ')。
这里向大家展示了如何不用宏来达到相应的效果,但要注意,这些不用宏的实现方式只适用于这些简单的示例。对于一些比较复杂的自动化操作,你确实应该考虑使用宏。
请参阅以下文档获取更多帮助:
1 | :h recording |
返回主目录 ⤴️
颜色主题
颜色主题可以把你的 Vim 变得更漂亮。Vim 是由多个组件构成的,我们可以给每一个组件都设置不同的文字颜色、背景颜色以及文字加粗等等。比如,我们可以通过这个命令来设置背景颜色:
1 | :highlight Normal ctermbg=1 guibg=red |
执行后你会发现,现在背景颜色变成红色了。请参阅 :h :highlight 来获取更多帮助。
其实,颜色主题就是一系列的 :highlight 命令的集合。
事实上,大部分颜色主题都包含两套配置。一套适用于例如 xterm 和 iTerm 这样的终端环境(使用前缀 cterm),另一套适用于例如 gvim 和 MacVim 的图形界面环境(使用前缀 gui)。对于上面的例子,ctermbg 就是针对终端环境的,而 guibg 就是针对图形界面环境的。
如果你下载了一个颜色主题,并且在终端环境中打开了 Vim,然后发现显示的颜色与主题截图中差别很大,那很可能是配置文件只设置了图形界面环境的颜色。反之同理,如果你使用的是图形界面环境,发现显示颜色有问题,那就很可能是配置文件只设置了终端环境的颜色。
第二种情况(图形界面环境的显示问题)其实不难解决。如果你使用的是 Neovim 或者 Vim 7.4.1830 的后续版本,可以通过打开真彩色设置来解决显示问题。这就可以让终端环境的 Vim 使用 GUI 的颜色定义,但首先,你要确认一下你的终端环境和环境内的组件(比如 tmux)是否都支持真彩色。可以看一下这篇文档,描述的十分详细。
请参阅以下文档或链接来获取更多帮助:
返回主目录 ⤴️
折叠
每一部分文字(或者代码)都会有特定的结构。对于存在结构的文字和代码,也就意味着它们可以按照一定的逻辑分割成不同区域。Vim 中的折叠功能,就是按照特定的逻辑把文字和代码折叠成一行,并显示一些简短的描述。折叠功能涉及到很多操作,而且折叠功能可以嵌套使用。
在 Vim 中,有以下 6 中折叠类型:
| 折叠方式 | 概述 |
|---|---|
| diff | 在「比较窗口」中折叠未改变的文本 |
| expr | 使用 'foldexpr' 来创建新的折叠逻辑 |
| indent | 基于缩进折叠 |
| manual | 使用 zf、zF 或 :fold 来自定义折叠 |
| marker | 根据特定的文本标记折叠(通常用于代码注释) |
| syntax | 根据语法折叠,比如折叠 if 代码块 |
注意:折叠功能可能会显著地影响性能。如果你在使用折叠功能的时候出现了打字卡顿之类的问题,请考虑使用 FastFold 插件。这个插件可以让 Vim 按需更新折叠内容,而不是一直调用。
请参阅以下文档获取更多帮助:
1 | :h usr_28 |
会话
如果你保存了当前的「视图」(请参阅 :h :mkview),那么当前窗口、配置和按键映射都会被保存下来(请参阅 :h :loadview)。
「会话」就是存储所有窗口的相关设置,以及全局设置。简单来说,就是给当前的 Vim 运行实例拍个照,然后把相关信息存储到会话文件中。存储之后的改动就不会在会话文件中显示,你只需要在改动后更新一下会话文件就可以了。
你可以把当前工作的「项目」存储起来,然后可以在不同的「项目」之间切换。
现在就来试试吧。打开几个窗口和标签,然后执行 :mksession Foo.vim。如果你没有指定文件名,那就会默认保存为 Session.vim。这个文件会保存在当前的目录下,你可以通过 :pwd 来显示当前路径。重启 Vim 之后,你只需要执行 :source Foo.vim,就可以恢复刚才的会话了。所有的缓冲区、窗口布局、按键映射以及工作路径都会恢复到保存时的状态。
其实 Vim 的会话文件就只是 Vim 命令的集合。你可以通过命令 :vs Foo.vim 来看看会话文件中究竟有什么。
你可以决定 Vim 会话中究竟要保存哪些配置,只需要设置一下 'sessionoptions' 就可以了。
为了方便开发,Vim 把最后一次调用或写入的会话赋值给了一个内部变量 v:this_session。
请参阅以下文档来获取更多帮助:
1 | :h Session |
局部化
以上提到的很多概念,都有一个局部化(非全局)的版本:
| 全局 | 局部 | 作用域 | 帮助文档 |
|---|---|---|---|
:set |
:setlocal |
缓冲区或窗口 | :h local-options |
:map |
:map <buffer> |
缓冲区 | :h :map-local |
:autocmd |
:autocmd * <buffer> |
缓冲区 | :h autocmd-buflocal |
:cd |
:lcd |
窗口 | :h :lcd |
:<leader> |
:<localleader> |
缓冲区 | :h maploacalleader |
变量也有不同的作用域,详细内容请参考 Vim scripting 的文档。
用法
获取离线帮助
Vim 自带了一套很完善的帮助文档,它们是一个个有固定排版格式的文本文件,通过标签可以访问这些文件的特定位置。
在开始之前先读一下这个章节::help :help。执行这个命令以后会在新窗口打开 $VIMRUNTIME/doc/helphelp.txt 文件并跳转到这个文件中 :help 标签的位置。
一些关于帮助主题的简单规则:
-
用单引号把文本包起来表示选项,如:
:h 'textwidth' -
以小括号结尾表示 VimL 函数,如:
:h reverse() -
以英文冒号开头表示命令,如:
:h :echo
使用快捷键 <c-d> (这是 ctrl+d)来列出所有包含你当前输入的内容的帮助主题。如::h tab<c-d> 会列出所有包含 tab 主题,从 softtabstop 到 setting-guitablabel (译者注:根据安装的插件不同列出的选项也会不同)。
你想查看所有的 VimL 方法吗?很简单,只要输入::h ()<c-d> 就可以了。你想查看所有与窗口相关的函数吗?输入 :h win*()<c-d>。
相信你很快就能掌握这些技巧,但是在刚开始的时候,你可能对于该通过什么进行查找一点线索都没有。这时你可以想象一些与要查找的内容相关的关键字,再让 :helpgrep 来帮忙。
1 | :helpgrep backwards |
上面的命令会在所有的帮助文件中搜索“backwards”,然后跳转到第一个匹配的位置。所有的匹配位置都会被添加到全局位置信息表,用 :cp / :cn 可以在匹配位置之间进行切换。或者用 :copen 命令来打开全局位置信息表,将光标定位到你想要的位置,再按 回车就可以跳转到该匹配项。详细说明请参考 :h quickfix。
获取离线帮助(补充)
这个列表最初发表在 vim_dev,由 @chrisbra 编辑的,他是 Vim 开发人员中最活跃的一个。
经过一些微小的改动后,重新发布到了这里。
如果你知道你想要找什么,使用帮助系统的搜索会更简单一些,因为搜索出的主题都带有固定的格式。
而且帮助系统中的主题包含了你当前使用的 Vim 版本的所特有特性,而网上那些已经过时或者是早期发布的话题是不会包含这些的。
因此学习使用帮助系统以及它所用的语言是很有必要的。这里是一些例子(不一定全,我有可能忘了一些什么)。
(译者注:下面列表中提及的都是如何指定搜索主题以便快速准确的找到你想要的帮助)
-
选项要用单引号引起来。用
:h 'list'来查看列表选项帮助。只有你明确的知道你要找这么一个选项的时候才可以这么做,不然的话你可以用:h options.txt来打开所有选项的帮助页面,再用正则表达式进行搜索,如:/width。某些选项有它们自己的命名空间,如::h cpo-a,:h cpo-A,:h cpo-b等等。 -
普通模式的命令不能用冒号作为前缀。使用
:h gt来转到“gt”命令的帮助页面。 -
正则表达式以“/”开头,所以
:h /\+会带你到正则表达式中量词“+”的帮助页面。 -
组合键经常以一个字母开头表示它们可以在哪些模式中使用。如:
:h i_CTRL-X会带你到插入模式下的 CTRL-X 命令的用法帮助页面,这是一个自动完成类的组合键。需要注意的是某些键是有固定写法的,如 Control 键写成 CTRL。还有,查找普通模式下的组合键帮助时,可以省略开头的字母“n”,如::h CTRL-A。而:h c_CTRL-A(译者注:原文为:h c_CRTL-R,感觉改为 A 更符合上下文语境)会解释 CTRL-A 在命令模式下输入命令时的作用;:h v_CTRL-A说的是在可见模式下把光标所在处的数字加 1;:h g_CTRL-A则说的是 g 命令(你需要先按 “g” 的命令)。这里的 “g” 代表一个普通的命令,这个命令总是与其它的按键组合使用才生效,与 “z” 开始的命令相似。 -
寄存器是以 “quote” 开头的。如:
:h quote:(译者注:原文为:h quote,感觉作者想以":“来举例)来查看关于”:"寄存器的说明。 -
关于 Vim 脚本(VimL)的帮助都在
:h eval.txt里。而某些方面的语言可以使用:h expr-X获取帮助,其中的 ‘X’ 是一个特定的字符,如::h expr-!会跳转到描述 VimL 中’!'(非)的章节。另外一个重要提示,可以使用:h function-list来查看所有函数的简要描述,列表中包括函数名和一句话描述。 -
关于映射都可以在
:h map.txt中找到。通过:h mapmode-i来查找:imap命令的相关信息;通过:h map-topic来查找专门针对映射的帮助(译者注:topic 为一个占位符,正如上面的字符 ‘X’ 一样,在实际使用中需要替换成相应的单词)(如::h :map-local查询本地 buffer 的映射,:h map-bar查询如何在映射中处理’|')。 -
命令定义用 “command-” 开头,如用
:h command-bar来查看自定义命令中’!'的作用。 -
窗口管理类的命令是以 “CTRL-W” 开头的,所以你可以用
:h CTRL-W_*来查找相应的帮助(译者注:'*'同样为占位符)(如::h CTRL-W_p查看切换到之前访问的窗口命令的解释)。如果你想找窗口处理的命令,还可以通过访问:h windows.txt并逐行向下浏览,所有窗口管理的命令都在这里了。 -
执行类的命令以":"开头,即:
:h :s讲的是 “😒” 命令。 -
在输入某个话题时按 CTRL-D,让 Vim 列出所有的近似项辅助你输入。
-
用
:helpgrep在所有的帮助页面(通常还包括了已安装的插件的帮助页面)中进行搜索。参考:h :helpgrep来了解如何使用。当你搜索了一个话题之后,所有的匹配结果都被保存到了全局位置信息表(或局部位置信息表)当中,可以通过:copen或:lopen打开。在打开的窗口中可能通过/对搜索结果进行进一步的过滤。 -
:h helphelp里介绍了如何使用帮助系统。 -
用户手册。它采用了一种对初学者更加友好的方式来展示帮助话题。用
:h usr_toc.txt打开目录(你可能已经猜到这个命令的用处了)。浏览用户手册能帮助你找出某些你想了解的话题,如你可以在第 24 章看到关于“复合字符”以及“输入特殊字符”的讲解(用:h usr_24.txt可以快速打开相关章节)。 -
高亮分组的帮助以
hl-开头。如::h hl-WarningMsg说的是警告信息分组的高亮。 -
语法高亮以
:syc-开头,如::h :syn-conceal讲的是:syn命令的对于隐藏字符是如何显示的。 -
快速修复命令以
:c开头,而位置列表命令以:l开头。 -
:h BufWinLeave讲的是 BufWinLeave 自动命令。还有,:h autocommand-events(译者注:原文是:h autocommands-events,但是没有该帮助)讲的是所有可用的事件。 -
启动参数都以“-”开头,如:
:h -f会告诉你 Vim 中 “-f” 参数的作用。 -
额外的特性都以“+”开头,如:
:h +conceal讲的是关于隐藏字符的支持。 -
错误代码可以在帮助系统中直接查到。
:h E297会带你到关于这一错误的详细解释。但是有时并没有转到错误描述,而是列出了经常导出这一错误的 Vim 命令,如:h E128(译者注:原文为:h hE128,但是并没有该帮助)会直接跳转到:function命令。 -
关于包含的语法文件的文档的帮助话题格式是
:h ft-*-syntax。如::h ft-c-syntax说的就是 C 语言语法文件以及它所提供的选项。有的语法文件还会带有自动完成(:h ft-php-omni)或文件类型插件(:h ft-tex-plugin)相关的章节可以查看。
另外在每个帮助页的顶端通常会包含一个用户文档链接(更多的从从用户的角度出发来主角命令的功能和用法,不涉及那么多细节)。如::h pattern.txt 里包含了 :h 03.9 和 :h usr_27 两个章节的链接。
获取在线帮助
如果你遇到了无法解决的问题,或者需要指引的话,可以参考 Vim 使用邮件列表。 IRC 也是一个很不错的资源。 Freenode 上的 #vim 频道很庞大,并且里面有许多乐于助人的人。
如果你想给 Vim 提交 Bug 的话,可以使用 vim_dev 邮件列表。
执行自动命令
你可以触发任何事件,如::doautocmd BufRead。
用户自定义事件
对于插件而言,创建你自己的自定义事件有时非常有用。
1 | function! Chibby() |
现在你插件的用户可以在 Chibby 执行完成之后做任何他想做的事情:
1 | autocmd User ChibbyExit call ChibbyCleanup() |
顺便提一句,如果在使用 :autocmd 或 :doautocmd 时没有捕捉异常,那么会输出 “No matching autocommands” 信息。这也是为什么许多插件用 silent doautocmd ... 的原因。但是这也会有不足,那就是你不能再在 :autocmd 中使用 echo "foo" 了,取而代之的是你要使用 unsilent echo "foo" 来输出。
这就是为什么要在触发事件之前先判断事件是否存在的原因,
1 | if exists('#User#ChibbyExit') |
帮助文档::h User
事件嵌套
默认情况下,自动命令不能嵌套!如果某个自动命令执行了一个命令,这个命令再依次触发其它的事件,这是不可能的。
例如你想在每次启动 Vim 的时候自动打开你的 vimrc 文件:
1 | autocmd VimEnter * edit $MYVIMRC |
当你启动 Vim 的时候,它会帮你打开你的 vimrc 文件,但是你很快会注意到这个文件没有任何的高亮,尽管平时它是正常可以高亮的。
问题在于你的非嵌套自动命令 :edit 不会触发“BufRead”事件,所以并不会把文件类型设置成“vim”,进而 $VIMRUNTIME/syntax/vim.vim 永远不会被引入。详细信息请参考::au BufRead *.vim。要想完成上面所说的需求,使用下面这个命令:
1 | autocmd VimEnter * nested edit $MYVIMRC |
帮助文档::h autocmd-nested
剪切板
如果你想在没有 GUI 支持的 Unix 系统中使用 Vim 的 'clipboard' 选项,则需要 +clipboard 以及可选的 +xterm_clipboard 两个特性支持。
帮助文档:
1 | :h 'clipboard' |
另外请参考:持续粘贴(为什么我每次都要设置 ‘paste’ 模式
剪贴板的使用(Windows, OSX)
在这两个系统中都可以用大家习惯用的 ctrl+c / cmd+c 复制选择的文本,然后在另外一个应用中用 ctrl+v / cmd+v 进行粘贴。
需要注意的是复制的文本已经被发送到了剪贴板,所以你在粘贴复制的内容之前关闭这个应用是没有任何问题的。
每次复制的时候,都会向剪贴板寄存器 * 中写入数据。 而在 Vim 中分别使用 "*y 和 "*p 来进行复制(yank) 和 粘贴(paste)。
如果你不想每次操作都要指定 * 寄存器,可以在你的 vimrc 中添加如下配置:
1 | set clipboard=unnamed |
通常情况下复制/删除/放入操作会往 " 寄存器中写入数据,而加上了上面的配置之后 * 寄存器也会被写入同样数据,因此简单的使用 y 和 p 就可以复制粘贴了。
我再说一遍:使用上面的选项意味着每一次的复制/粘贴,即使在同一个 Vim 窗口里,都会修改剪贴板的内容。你自己决定上面的选项是否适合。
如果你觉得输入 y 还是太麻烦的话,可以使用下面的设置把在可视模式下选择的内容发送到剪贴板:
1 | set clipboard=unnamed,autoselect |
帮助文档:
1 | :h clipboard-unnamed |
剪贴板的使用(Linux, BSD, …)
如果你的系统使用了 X 图形界面,事情会变得有一点不同。X 图形界面实现了 X 窗口系统协议, 这个协议在 1987 年发布的主版本 11,因此 X 也通常被称为 X11。
在 X10 版本中,剪贴缓冲区被用来实现像 clipboard 一样由 X 来复制文本,并且可以被所有的程序访问。现在这个机制在 X 中还存在,但是已经过时了,很多程序都不再使用这一机制。
近年来数据在程序之间是通过选择进行传递的。一共有三种选择,经常用到的有两种:PRIMARY 和 CLIPBOARD。
选择的工作工模大致是这样的:
Program A:<ctrl+c>
Program A:声称对 CLIPBOARD 的所有权
Program B:<ctrl+v>
Program B:发现CLIPBOARD的所有权被Program A持有
Program B:从Program A请求数据
Program A:响应这个请求并发送数据给Program B
Program B:从Program A接收数据并插入到窗口中
| 选择 | 何时使用 | 如何粘贴 | 如何在 Vim 中访问 |
|---|---|---|---|
| PRIMARY | 选择文本 | 鼠标中键, shift+insert | * 寄存器 |
| CLIPBOARD | 选择文本并按 ctrl+c |
ctrl+v |
+寄存器 |
注意:X 服务器并不会保存选择(不仅仅是 CLIPBOARD 选择)!因此在关闭了相应的程序后,你用 ctrl+c 复制的内容将丢失。
使用 "*p 来贴粘 PRIMARY 选择中的内容,或者使用 "+y1G 来将整个文件的内容复制到 CLIPBOARD 选择。
如果你需要经常访问这两个寄存器,可以考虑使用如下配置:
1 | set clipboard^=unnamed " * 寄存器 |
(^= 用来将设置的值加到默认值之前,详见::h :set^=)
这会使得所有复制/删除/放入操作使用 * 或 + 寄存器代替默认的未命令寄存器 "。之后你就可以直接使用 y 或 p 访问你的 X 选择了。
帮助文档:
1 | :h clipboard-unnamed |
打开文件时恢复光标位置
如果没有这个设置,每次打开文件时光标都将定位在第一行。而加入了这个设置以后,你就可以恢复到上次关闭文件时光标所在的位置了。
将下面的配置添加到你的 vimrc 文件:
1 | autocmd BufReadPost * |
这是通过判断之前的光标位置是否存在(文件可能被其它程序修改而导致所记录的位置已经不存在了),如果存在的话就执行 g`" (转到你离开时的光标位置但是不更改跳转列表)。
这需要使用 viminfo 文件::h viminfo-。
临时文件
根据选项的不同, Vim 最多会创建 4 种工作文件。
备份文件
你可以让 Vim 在将修改写入到文件之前先备份原文件。默认情况下, Vim 会保存一个备份文件但是当修改成功写入后会立即删除它(:set writebackup)。如果你想一直保留这个备份文件的话,可以使用 :set backup。而如果你想禁用备份功能的话,可以使用 :set nobackup nowritebackup。
咱们来看一下上次我在 vimrc 中改了什么:
1 | $ diff ~/.vim/vimrc ~/.vim/files/backup/vimrc-vimbackup |
帮助文档::h backup
交换文件
假设你有一个非常棒的科幻小说的构思。在按照故事情节已经写了好几个小时几十万字的时候…忽然停电了!而那时你才想起来你上次保存 ~/来自外太空的邪恶入侵者.txt 是在… 好吧,你从来没有保存过。
但是并非没有希望了!在编辑某个文件的时候, Vim 会创建一个交换文件,里面保存的是对当前文件所有未保存的修改。自己试一下,打开任意的文件,并使用 :swapname 获得当前的交换文件的保存路径。你也可以将 :set noswapfile 加入到 vimrc 中来禁用交换文件。
默认情况下,交换文件会自动保存在被编辑文件所在的目录下,文件名以 .file.swp 后缀结尾,每当你修改了超过 200 个字符或是在之前 4 秒内没有任何动作时更新它的内容,在你不再编辑这个文件的时候会被删除。你可以自己修改这些数字,详见::h 'updatecount' 和 :h 'updatetime'。
而在断电时,交换文件并不会被删除。当你再次打开 vim ~/来自外太空的邪恶入侵者.txt 时, Vim 会提示你恢复这个文件。
帮助文档::h swap-file 和 :h usr_11
撤销文件
内容变更历史记录是保存在内存中的,并且会在 Vim 退出时清空。如果你想让它持久化到磁盘中,可以设置 :set undofile。这会把文件 ~/foo.c 的撤销文件保存在 ~/foo.c.un~。
帮助文档::h 'undofile' 和 :h undo-persistence
viminfo 文件
备份文件、交换文件和撤销文件都是与文本状态相关的,而 viminfo 文件是用来保存在 Vim 退出时可能会丢失的其它的信息的。包括历史记录(命令历史、搜索历史、输入历史)、寄存器内容、标注、缓冲区列表、全局变量等等。
默认情况下,viminfo 被保存在 ~/.viminfo。
帮助文档::h viminfo 和 :h 'viminfo'
临时文件管理设置示例
如果你跟我一样,也喜欢把这些文件放到一个位置(如:~/.vim/files)的话,可以使用下面的配置:
1 | " 如果文件夹不存在,则新建文件夹 |
注意:如果你在一个多用户系统中编辑某个文件时, Vim 提示你交换文件已经存在的话,可能是因为有其他的用户此时正在编辑这个文件。而如果将交换文件放到自己的 home 目录的话,这个功能就失效了。因此服务器非常不建议将这些文件修改到 HOME 目录,避免多人同时编辑一个文件,却没有任何警告。
编辑远程文件
Vim 自带的 netrw 插件支持对远程文件的编辑。实际上它将远程的文件通过 scp 复制到本地的临时文件中,再用那个文件打开一个缓冲区,然后在保存时把文件再复制回远程位置。
下面的命令在你本地的 VIM 配置与 SSH 远程服务器上管理员想让你使用的配置有冲突时尤其有用:
1 | :e scp://bram@awesome.site.com/.vimrc |
如果你已经设置了 ~/.ssh/config,SSH 会自动读取这里的配置:
Host awesome
HostName awesome.site.com
Port 1234
User bram
如果你的 ~/.ssh/config 中有以上的内容,那么下面的命令就可以正常执行了:
1 | :e scp://awesome/.vimrc |
可以用同样的方法编辑 ~/.netrc, 详见::h netrc-netrc。
确保你已经看过了 :h netrw-ssh-hack 和 :h g:netrw_ssh_cmd。
另外一种编辑远程文件的方法是使用 sshfs,它会用 FUSE 来挂载远程的文件系统到你本地的系统当中。
插件管理
Pathogen是第一个比较流行的插件管理工具。实际上它只是修改了 runtimepath (:h 'rtp') 来引入所有放到该目录下的文件。你需要自己克隆插件的代码仓库到那个目录。
真正的插件管理工具会在 Vim 中提供帮助你安装或更新插件的命令。以下是一些常用的插件管理工具:
多行编辑
这是一种可以同时输入多行连续文本的技术。参考这个示例。
用 <c-v> 切换到可视块模式。然后向下选中几行,按 I 或 A (译者注:大写字母,即 shift+i 或 shift+a)然后开始输入你想要输入的文本。
在刚开始的时候可能会有些迷惑,因为文本只出现在了当前编辑的行,只有在当前的插入动作结束后,之前选中的其它行才会出现插入的文本。
举一个简单的例子:<c-v>3jItext<esc>。
如果你要编辑的行长度不同,但是你想在他们后面追加相同的内容的话,可以试一下这个:<c-v>3j$Atext<esc>。
有时你可能需要把光标放到当前行末尾之后,默认情况下你是不可能做到的,但是可能通过设置 virtualedit 选项达到目的:
1 | set virtualedit=all |
设置之后 $10l 或 90| 都会生效,即使超过了行尾的长度。
详见 :h blockwise-examples。在开始的时候可能会觉得有些复杂,但是它很快就会成为你的第二天性的。
如果你想探索更有趣的事情,可以看看多光标
使用外部程序和过滤器
免责声明:Vim 是单线程的,因此在 Vim 中以前端进程执行其它的程序时会阻止其它的一切。当然你可以使用 Vim 程序接口,如 Lua,并且使用它的多线程支持,但是在那期间, Vim 的处理还是被阻止了。Neovim 添加了任务 API 解决了此问题。
(据说 Bram 正在考虑在 Vim 中也添加任务控制。如果你使用了较新版本的的 Vim ,可以看一下 :helpgrep startjob。)
使用 :! 启动一个新任务。如果你想列出当前工作目录下的所有文件,可以使用 :!ls。 用 | 来将结果通过管道重定向,如::!ls -l | sort | tail -n5。
没有使用范围时(译者注:范围就是 : 和 ! 之间的内容,. 表示当前行,+4 表示向下偏移 4 行,$ 表示最末行等,多行时用 , 将它们分开,如 .,$ 表示从当前行到末行),:! 会显示在一个可滚动的窗口中(译者注:在 GVim 和在终端里运行的结果稍有不同)。相反的,如果指定了范围,这些行会被过滤。这意味着它们会通过管道被重定向到过滤程序的 stdin,在处理后再通过过滤程序的 stdout 输出,用输出结果替换范围内的文本。例如:为接下来的 5 行文本添加行号,可以使用:
1 | :.,+4!nl -ba -w1 -s' ' |
由于手动添加范围很麻烦, Vim 提供了一些辅助方法以方便的添加范围。如果需要经常带着范围的话,你可以在可见模式中先选择,然后再按 : (译者注:选中后再按 ! 更方便)。还可以使用 ! 来取用一个 motion 的范围,如 !ipsort (译者注:原文为 !ip!sort ,但经过实验发现该命令执行报错,可能是因为 Vim 版本的原因造成的,新版本使用 ip 选择当前段落后自动在命令后添加了 ! ,按照作者的写法来看,可能之前的版本没有自动添加 ! )可以将当前段落的所有行按字母表顺序进行排序。
一个使用过滤器比较好的案例是Go 语言。它的缩进语法非常个性,甚至还专门提供了一个名为 gofmt 的过滤器来对 Go 语言的源文件进行正确的缩进。Go 语言的插件通常会提供一个名为 :Fmt 的函数,这个函数就是执行了 :%!gofmt 来对整个文件进行缩进。
人们常用 :r !prog 将 prog 程序的插入放到当前行的下面,这对于脚本来说是很不错的选择,但是在使用的过程中我发现 !!ls 更加方便,它会用输出结果替换当前行的内容。(译者注:前面命令中的 prog 只是个占位符,在实际使用中需要替换成其它的程序,如 :r !ls,这就与后面的 !!ls 相对应了,两者唯一的不同是第一个命令不会覆盖当前行内容,但是第二个命令会)
帮助文档:
1 | :h filter |
Cscope
Cscope 的功能比 ctags 要完善,但是只支持 C(通过设置 cscope.files 后同样支持 C++以及 Java)。
鉴于 Tag 文件只是知道某个符号是在哪里定义的,cscope 的数据库里的数据信息就多的多了:
-
符号是在哪里定义的?
-
符号是在哪里被使用的?
-
这个全局符号定义了什么?
-
这个变量是在哪里被赋值的?
-
这个函数在源文件的哪个位置?
-
哪些函数调用了这个函数?
-
这个函数调用了哪些函数?
-
"out of space"消息是从哪来的?
-
在目录结构中当前的源文件在哪个位置?
-
哪些文件引用了这个头文件?
1. 构建数据库
在你项目的根目录执行下面的命令:
1 | $ cscope -bqR |
这条命令会在当前目录下创建三个文件:cscope{,.in,.po}.out 。把它们想象成你的数据库。
不幸的时 cscope 默认只分析 *.[c|h|y|l] 文件。如果你想在 Java 项目中使用 cscope ,需要这样做:
1 | $ find . -name "*.java" > cscope.files |
2. 添加数据库
打开你新创建的数据库连接:
1 | :cs add cscope.out |
检查连接已经创建成功:
1 | :cs show |
(当然你可以添加多个连接。)
3. 查询数据库
1 | :cs find <kind> <query> |
如::cs find d foo 会列出 foo(...) 调用的所有函数。
| Kind | 说明 |
|---|---|
| s | symbol:查找使用该符号的引用 |
| g | global:查找该全局符号的定义 |
| c | calls:查找调用当前方法的位置 |
| t | text:查找出现该文本的位置 |
| e | egrep:使用 egrep 搜索当前单词 |
| f | file:打开文件名 |
| i | includes:查询引入了当前文件的文件 |
| d | depends:查找当前方法调用的方法 |
推荐一些比较方便的映射,如:
1 | nnoremap <buffer> <leader>cs :cscope find s <c-r>=expand('<cword>')<cr><cr> |
所以 :tag (或 <c-]>)跳转到标签定义的文件,而 :cstag 可以达到同样的目的,同时还会打开 cscope 的数据库连接。'cscopetag' 选项使得 :tag 命令自动的像 :cstag 一样工作。这在你已经使用了基于标签的映射时会非常方便。
帮助文档::h cscope
MatchIt
由于 Vim 是用 C 语言编写的,因此许多功能都假设使用类似 C 语言的语法。默认情况下,如果你的光标在 { 或 #endif , 就可以使用 % 跳转到与之匹配的 } 或 #ifdef。
Vim 自带了一个名为 matchit.vim 的插件,但是默认没有启用。启用后可以用 % 在 HTML 相匹配的标签或 VimL 的 if/else/endif 块之间进行跳转,它还带来了一些新的命令。
在 Vim 8 中安装
1 | " vimrc |
在 Vim 7 或者更早的版本中安装
1 | "vimrc |
由于 matchit 的文档很全面,我建议安装以后执行一次下面的命令:
1 | :!mkdir -p ~/.vim/doc |
简短的介绍
至此这个插件已经可以使用了。 参考 :h matchit-intro 来获得支持的命令以及 :h matchit-languages 来获得支持的语言。
你可以很方便的定义自己的匹配对,如:
1 | autocmd FileType python let b:match_words = '\<if\>:\<elif\>:\<else\>' |
之后你就可以在任何的 Python 文件中使用 % (向前)或 g% (向后)在这三个片断之间跳转了。
帮助文档:
1 | :h matchit-install |
技巧
跳至选择的区域另一端
在使用 v 或者 V 选择某段文字后,可以用 o 或者 O 按键跳至选择区域的开头或者结尾。
1 | :h v_o |
聪明地使用 n 和 N
n 与 N 的实际跳转方向取决于使用 / 还是 ? 来执行搜索,其中 / 是向后搜索,? 是向前搜索。一开始我(原作者)觉得这里很难理解。
如果你希望 n 始终为向后搜索,N 始终为向前搜索,那么只需要这样设置:
1 | nnoremap <expr> n 'Nn'[v:searchforward] |
聪明地使用命令行历史
我(原作者)习惯用 Ctrl + p 和 Ctrl + n 来跳转到上一个/下一个条目。其实这个操作也可以用在命令行中,快速调出之前执行过的命令。
不仅如此,你会发现 上 和 下 其实更智能。如果命令行中已经存在了一些文字,我们可以通过按方向键来匹配已经存在的内容。比如,命令行中现在是 :echo,这时候我们按 上,就会帮我们补全成 :echo "Vim rocks!"(前提是,之前输入过这段命令)。
当然,Vim 用户都不愿意去按方向键,事实上我们也不需要去按,只需要设置这样的映射:
1 | cnoremap <c-n> <down> |
这个功能,我(原作者)每天都要用很多次。
智能 Ctrl-l
Ctrl + l 的默认功能是清空并「重新绘制」当前的屏幕,就和 :redraw! 的功能一样。下面的这个映射就是执行重新绘制,并且取消通过 / 和 ? 匹配字符的高亮,而且还可以修复代码高亮问题(有时候,由于多个代码高亮的脚本重叠,或者规则过于复杂,Vim 的代码高亮显示会出现问题)。不仅如此,还可以刷新「比较模式」(请参阅 :help diff-mode)的代码高亮:
1 | nnoremap <leader>l :nohlsearch<cr>:diffupdate<cr>:syntax sync fromstart<cr><c-l> |
禁用错误报警声音和图标
1 | set noerrorbells |
请参阅 Vim Wiki: Disable beeping。
快速移动当前行
有时,我(原作者)想要快速把当前行上移或下移一行,只需要这样设置映射:
1 | nnoremap [e :<c-u>execute 'move -1-'. v:count1<cr> |
这个映射,同样可以搭配数字使用,比如连续按下 2 ] e 就可以把当前行向下移动两行。
快速添加空行
1 | nnoremap [<space> :<c-u>put! =repeat(nr2char(10), v:count1)<cr>'[ |
设置之后,连续按下 5 [ 空格 在当前行上方插入 5 个空行。
运行时检测
需要的特性:+profile
Vim 提供了一个内置的运行时检查功能,能够找出运行慢的代码。
:profile 命令后面跟着子命令来确定要查看什么。
如果你想查看所有的:
1 | :profile start /tmp/profile.log |
Vim 不断地在内存中检查信息,只在退出的时候输出出来。(Neovim 已经解决了这个问题用 :profile dump 命令)
看一下 /tmp/profile.log 文件,检查时运行的所有代码都会被显示出来,包括每一行代码运行的频率和时间。
大多数代码都是用户不熟悉的插件代码,如果你是在解决一个确切的问题,直接跳到这个日志文件的末尾,那里有 FUNCTIONS SORTED ON TOTAL TIME 和 FUNCTIONS SORTED ON SELF TIME 两个部分,如果某个 function 运行时间过长一眼就可以看到。
查看启动时间
感觉 Vim 启动的慢?到了研究几个数字的时候了:
1 | vim --startuptime /tmp/startup.log +q && vim /tmp/startup.log |
第一栏是最重要的因为它显示了绝对运行时间,如果在前后两行之间时间差有很大的跳跃,那么是第二个文件太大或者含有需要检查的错误的 VimL 代码。
NUL 符用新行表示
文件中的 NUL 符 (\0),在内存中被以新行(\n)保存,在缓存空间中显示为 ^@。
更多信息请参看 man 7 ascii 和 :h NL-used-for-Nul 。
快速编辑自定义宏
这个功能真的很实用!下面的映射,就是在一个新的命令行窗口中读取某一个寄存器(默认为 *)。当你设置完成后,只需要按下 回车 即可让它生效。
在录制宏的时候,我经常用这个来更改拼写错误。
1 | nnoremap <leader>m :<c-u><c-r><c-r>='let @'. v:register .' = '. string(getreg(v:register))<cr><c-f><left> |
只需要连续按下 leader m 或者 " leader m 就可以调用了。
请注意,这里之所以要写成 <c-r><c-r> 是为了确保 <c-r> 执行了。请参阅 :h c_^R^R
快速跳转到源(头)文件
这个技巧可以用在多种文件类型中。当你从源文件或者头文件中切换到其他文件的时候,这个技巧可以设置「文件标记」(请参阅 :h marks),然后你就可以通过连续按下 ‘ C 或者 ’ H 快速跳转回去(请参阅 :h 'A)。
1 | autocmd BufLeave *.{c,cpp} mark C |
注意:由于这个标记是设置在 viminfo 文件中,因此请先确认 :set viminfo? 中包含了 :h viminfo-'。
在 GUI 中快速改变字体大小
印象中,我(原作者)记得一下代码是来自 tpope’s 的配置文件:
1 | command! Bigger :let &guifont = substitute(&guifont, '\d\+$', '\=submatch(0)+1', '') |
根据模式改变光标类型
我(原作者)习惯在普通模式下用块状光标,在插入模式下用条状光标(形状类似英文 “I” 的样子),然后在替换模式中使用下划线形状的光标。
1 | if empty($TMUX) |
原理很简单,就是让 Vim 在进入和离开插入模式的时候,输出一些序列,请参考 escape sequence。Vim 与终端之间的中间层,比如 tmux 会处理并执行上面的代码。
但上面这个还是有一个缺点的。终端环境的内部原理不尽相同,对于序列的处理方式也稍有不同。因此,上面的代码可能无法在你的环境中运行。甚至,你的运行环境也有可能不支持其他光标形状,请参阅你的 Vim 运行环境的文档。
好消息是,上面这个代码,可以在 iTerm2 中完美运行。
防止水平滑动的时候失去选择
如果你选中了一行或多行,那么你可以用 < 或 > 来调整他们的缩进。但在调整之后就不会保持选中状态了。
你可以连续按下 g v 来重新选中他们,请参考 :h gv。因此,你可以这样来配置映射:
1 | xnoremap < <gv |
设置好之后,在可视模式中使用 >>>>> 就不会再出现上面提到的问题了。
选择当前行至结尾,排除换行符
在 Vim 里,我们可以同过 v$ 选择当前行至结尾,但此时会把最后一个换行符也选中,通常需要按额外的 h 来取消最后选中最后一个换行符号。
Vim 提供了一个 g_ 快捷键,可以移动光标至最后一个非空字符。因此,为达到次效果,可以使用 vg_。当然,如果觉得按三个键比较麻烦,可以添加一个映射:
1 | nnoremap L g_ |
这样就可以通过 vL 达到一样的效果了。
重新载入保存文件
通过自动命令,你可以在保存文件的同时触发一些其他功能。比如,如果这个文件是一个配置文件,那么就重新载入;或者你还可以对这个文件进行代码风格检查。
1 | autocmd BufWritePost $MYVIMRC source $MYVIMRC |
更加智能的当前行高亮
我(原作者)很喜欢「当前行高亮」(请参阅 :h cursorline)这个功能,但我只想让这个效果出现在当前窗口,而且在插入模式中关闭这个效果:
1 | autocmd InsertLeave,WinEnter * set cursorline |
更快的关键字补全
关键字补全(<c-n> 或 <c-p>)功能的工作方式是,无论 'complete' 设置中有什么,它都会尝试着去补全。这样,一些我们用不到的标签也会出现在补全列表中。而且,它会扫描很多文件,有时候运行起来非常慢。如果你不需要这些,那么完全可以像这样把它们禁用掉:
1 | set complete-=i " disable scanning included files |
改变颜色主题的默认外观
如果你想让状态栏在颜色主题更改后依然保持灰色,那么只需要这样设置:
1 | autocmd ColorScheme * highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE |
同理,如果你想让某一个颜色主题(比如 “lucius”)的状态栏为灰色(请使用 :echo color_name 来查看当前可用的所有颜色主题):
1 | autocmd ColorScheme lucius highlight StatusLine ctermbg=darkgray cterm=NONE guibg=darkgray gui=NONE |
命令
下面的命令都比较有用,最好了解一下。用 :h :<command name> 来了解更多关于它们的信息,如::h :global。
:global 和 :vglobal - 在所有匹配行执行命令
在所有符合条件的行上执行某个命令。如: :global /regexp/ print 会在所有包含 “regexp” 的行上执行 print 命令(译者注:regexp 有正则表达式的意思,该命令同样支持正则表达式,在所有符合正则表达式的行上执行指定的命令)。
趣闻:你们可能都知道老牌的 grep 命令,一个由 Ken Thompson 编写的过滤程序。它是干什么用的呢?它会输出所有匹配指定正则表达式的行!现在猜一下 :global /regexp/ print 的简写形式是什么?没错!就是 :g/re/p 。 Ken Thompsom 在编写 grep 程序的时候是受了 vi :global 的启发。(译者注: https://robots.thoughtbot.com/how-grep-got-its-name)
既然它的名字是 :global,理应仅作用在所有行上,但是它也是可以带范围限制的。假设你想使用 :delete 命令删除从当前行到下一个空行(由正则表达式 ^$ 匹配)范围内所有包含 “foo” 的行:
1 | :,/^$/g/foo/d |
如果要在所有 不 匹配的行上执行命令的话,可以使用 :global! 或是它的别名 :vglobal ( V 代表的是 inVerse )。
:normal 和 :execute - 脚本梦之队
这两个命令经常在 Vim 的脚本里使用。
借助于 :normal 可以在命令行里进行普通模式的映射。如::normal! 4j 会令光标下移 4 行(由于加了"!",所以不会使用自定义的映射 “j”)。
需要注意的是 :normal 同样可以使用范围数(译者注:参考 :h range 和 :h :normal-range 了解更多),故 :%norm! Iabc 会在所有行前加上 “abc”。
借助于 :execute 可以将命令和表达式混合在一起使用。假设你正在编辑一个 C 语言的文件,想切换到它的头文件:
1 | :execute 'edit' fnamemodify(expand('%'), ':r') . '.h' |
(译者注:头文件为与与源文件同名但是扩展名为 .h 的文件。上面的命令中 expand 获得当前文件的名称,fnamemodify 获取不带扩展名的文件名,再连上 ‘.h’ 就是头文件的文件名了,最后在使用 edit 命令打开这个头文件。)
这两个命令经常一起使用。假设你想让光标下移 n 行:
1 | :let n = 4 |
重定向消息
许多命令都会输出消息,:redir 用来重定向这些消息。它可以将消息输出到文件、寄存器或是某个变量中。
1 | " 将消息重定向到变量 `neatvar` 中 |
再 Vim 8 中,可以更简单的方式即位:
:put =execute('reg')
(译者注:原文最后一条命令是 :put =nicevar 但是实际会报变量未定义的错误)(实测 neovim/vim8 下没问题)
帮助文档::h :redir
调试
常规建议
如果你遇到了奇怪的行为,尝试用这个命令重现它:
vim -u NONE -N
这样会在不引用 vimrc(默认设置)的情况下重启 vim,并且在 nocompatible 模式下(使用 vim 默认设置而不是 vi 的)。(搜索 :h --noplugin 命令了解更多启动加载方式)
如果仍旧能够出现该错误,那么这极有可能是 vim 本身的 bug,请给 vim_dev 发送邮件反馈错误,多数情况下问题不会立刻解决,你还需要进一步研究
许多插件经常会提供新的(默认的/自动的)操作。如果在保存的时候发生了,那么请用 :verb au BufWritePost 命令检查潜在的问题
如果你在使用一个插件管理工具,将插件行注释调,再进行调试。
问题还没有解决?如果不是插件的问题,那么肯定是你的自定义的设置的问题,可能是你的 options 或 autocmd 等等。
到了一行行代码检查的时候了,不断地排除缩小检查范围知道你找出错误,根据二分法的原理你不会花费太多时间的。
在实践过程中,可能就是这样,把 :finish 放在你的 vimrc 文件中间,Vim 会跳过它之后的设置。如果问题还在,那么问题就出在:finish之前的设置中,再把:finish放到前一部分设置的中间位置。否则问题就出现在它后面的半部分设置,那么就把:finish放到后半部分的中间位置。不断的重复即可找到。
调整日志等级
Vim 现在正在使用的另一个比较有用的方法是增加 debug 信息输出详细等级。现在 Vim 支持 9 个等级,可以用:h 'verbose'命令查看。
1 | :e /tmp/foo |
这可以显示出所有引用的文件、没有变化的文件或者各种各样的作用于保存的插件。
如果你只是想用简单的命令来提高等级,也是用 :verbose ,放在其他命令之前,通过计数来指明等级,默认是 1.
1 | :verb set verbose |
通常用等级 1 来显示上次从哪里设置的选项
1 | :verb set ai? |
一般等级越高输出信息月详细。但是不要害怕,亦可以把输出导入到文件中:
1 | :set verbosefile=/tmp/foo | 15verbose echo "foo" | vsplit /tmp/foo |
你可以一开始的时候就打开 verbosity,用 -V 选项,它默认设置调试等级为 10。 例如:vim -V5
查看启动日志
查看运行时日志
Vim 脚本调试
如果你以前使用过命令行调试器的话,对于:debug命令你很快就会感到熟悉。
只需要在任何其他命令之前加上:debug就会让你进入调试模式。也就是,被调试的 Vim 脚本会在第一行停止运行,同时该行会被显示出来。
想了解可用的 6 个调试命令,可以查阅:h >cont和阅读下面内容。需要指出的是,类似 gdb 和其他相似调试器,调试命令可以使用它们的简短形式:c、 q、n、s、 i和 f。
除了上面的之外,你还可以自由地使用任何 Vim 的命令。比如,:echo myvar,该命令会在当前的脚本代码位置和上下文上被执行。
只需要简单使用:debug 1,你就获得了REPL调试特性。
当然,调试模式下是可以定义断点的,不然的话每一行都去单步调试就会十分痛苦。(断点之所以被叫做断点,是因为运行到它们的时候,运行就会停止下来。因此,你可以利用断点跳过自己不感兴趣的代码区域)。请查阅:h :breakadd、 :h :breakdel和 :h :breaklist获取更多细节。
假设你需要知道你每次在保存一个文件的时候有哪些代码在运行:
1 | :au BufWritePost |
正如你所见,使用<cr>命令会重复之前的调试命令,也就是在该例子中的s命令。
:debug命令可以和verbose选项一起使用。
语法文件调试
语法文件由于包含错误的或者复制的正则表达式,常常会使得 Vim 的运行较慢。如果 Vim 在编译的时候包含了+profile feature特性,就可以给用户提供一个超级好用的:syntime命令。
1 | :syntime on |
输出结果包含了很多的度量维度。比如,你可以通过结果知道哪些正则表达式耗时太久需要被优化;哪些正则表达式一直在别使用但重来没有一次成功匹配。
请查阅:h :syntime。
杂项
附加资源
| 资源名称 | 简介 |
|---|---|
| 七个高效的文本编辑习惯 | 作者:Bram Moolenaar(即 Vim 的作者) |
| 七个高效的文本编辑习惯 2.0(PDF 版) | 同上 |
| IBM DeveloperWorks: 使用脚本编写 Vim 编辑器 | Vim 脚本编写五辑 |
| 《漫漫 Vim 路》 | 使用魔抓定制 Vim 插件 |
| 《 Vim 实践 (第 2 版)》 | 轻取 Vim 最佳书籍 |
| Vimcasts.org | Vim 录屏演示 |
| 为什么是个脚本都用 vi? | 常见误区释疑 |
| 你不爱 vi,所以你不懂 Vim | 简明,扼要,准确的干货 |
Vim 配置集合
目前,网上有很多流行 Vim 配置集合,对于 Vim 配置集合,个人认为有利有弊。对于维护的比较好的配置,比如 SpaceVim 还是值得尝试的,可以节省很多自行配置的时间。当然,网上还有很多其他很流行的配置,比如:
常见问题
编辑小文件时很慢
有两个因素对性能影响非常大:
-
过于复杂的 正则表达式 。尤其是 Ruby 的语法文件,以前会造成性能下降。(见调试语法文件)
-
屏幕重绘 。有一些功能会强制重绘所有行。
| 典型肇事者 | 原因 | 解决方案 |
|---|---|---|
:set cursorline |
会导致所有行重绘 | :set nocursorline |
:set cursorcolumn |
会导致所有行重绘 | :set nocursorcolumn |
:set relativenumber |
会导致所有行重绘 | :set norelativenumber |
:set foldmethod=syntax |
如果语法文件已经很慢了,这只会变得更慢 | :set foldmethod=manual,:set foldmethod=marker 或者使用快速折叠插件 |
:set synmaxcol=3000 |
由于内部表示法,Vim 处理比较长的行时会有问题。让它高亮到 3000 列…… | :set synmaxcol=200 |
| matchparen.vim | Vim 默认加载的插件,用正则表达式查找配对的括号 | 禁用插件::h matchparen |
注意:只有在你真正遇到性能问题的时候才需要做上面的调整。在大多数情况下使用上面提到的选项是完全没有问题的。
编辑大文件的时候很慢
Vim 处理大文件最大的问题就是它会一次性读取整个文件。这么做是由于缓冲区的内部机理导致的(在 vim_dev 中讨论)。
如果只是想查看的话,tail hugefile | vim - 是一个不错的选择。
如果你能接受没有语法高亮,并且禁用所有插件和设置的话,使用:
1 | $ vim -u NONE -N |
这将会使得跳转变快很多,尤其是省去了基于很耗费资源的正则表达式的语法高亮。你还可以告诉 Vim 不要使用交换文件和 viminfo 文件,以避免由于写这些文件而造成的延时:
1 | $ vim -n -u NONE -i NONE -N |
简而言之,尽量避免使用 Vim 写过大的文件。
持续粘贴(为什么我每次都要设置 ‘paste’ 模式)
持续粘贴模式让终端模拟器可以区分输入内容与粘贴内容。
你有没有遇到过往 Vim 里粘贴代码之后被搞的一团糟?
这在你使用 cmd+v、shirt-insert、middle-click 等进行粘贴的时候才会发生。因为那样的话你只是向终端模拟器扔了一大堆的文本。
Vim 并不知道你刚刚是粘贴的文本,它以为你在飞速的输入。于是它想缩进这些行但是失败了。
这明显不是个问题,如果你用 Vim 的寄存器粘贴,如:"+p ,这时 Vim 就知道了你在粘贴,就不会导致格式错乱了。
使用 :set paste 就可以解决这个问题正常进行粘贴。见 :h 'paste' 和 :h 'pastetoggle' 获取更多信息。
如果你受够了每次都要设置 'paste' 的话,看看这个能帮你自动设置的插件:bracketed-paste。
点此查看该作者对于这个插件的更多描述。
Neovim 尝试把这些变得更顺畅,如果终端支持的话,它会自动开启持续粘贴模式,无须再手动进行切换。
在终端中按 ESC 后有延时
如果你经常使用命令行,那么肯定要接触 终端模拟器 ,如 xterm、gnome-terminal、iTerm2 等等(与实际的终端不同)。
终端模拟器与他们的祖辈一样,使用 转义序列 (也叫 控制序列 )来控制光标移动、改变文本颜色等。转义序列就是以转义字符开头的 ASCII 字符串(用脱字符表示法表示成 ^[ )。当遇到这样的字符串后,终端模拟器会从终端信息数据库中查找对应的动作。
为了使用问题更加清晰,我会先来解释一下什么是映射超时。在映射存在歧义的时候就会产生映射超时:
1 | :nnoremap ,a :echo 'foo'<cr> |
上面的例子中两个映射都能正常工作,但是当输入 ,a 之后,Vim 会延时 1 秒,因为它要确认用户是否还要输入那个 b。
转义序列会产生同样的问题:
-
<esc>作为返回普通模式或取消某个动作的按键而被大量使用 -
光标键使用转义序列进行的编码
-
Vim 期望 Alt (也叫作 Mate Key )会发送一个正确的 8-bit 编码的高位,但是许多终端模拟器并不支持这个(也可能默认没有启用),而只是发送一个转义序列作为代替。
你可以这样测试上面所提到的事情: vim -u NONE -N 然后输入 i<c-v><left> ,你会看到一个以 ^[ 开头的字符串,表明这是一个转义序列,^[ 就是转义字符。
简而言之,Vim 在区分录入的 <esc> 和转义序列的时候需要一定的时间。
默认情况下,Vim 用 :set timeout timeoutlen=1000,就是说它会用 1 秒的时间来区分有歧义的映射 以及 按键编码。这对于映射来说是一个比较合理的值,但是你可以自行定义按键延时的长短,这是解决该问题最根本的办法:
1 | set timeout " for mappings |
在 :h ttimeout 里你可以找到一个关于这些选项之间关系的小表格。
而如果你在 tmux 中使用 Vim 的话,别忘了把下面的配置加入到你的 ~/.tmux.conf文件中:
set -sg escape-time 0
无法重复函数中执行的搜索
-
在命令中的搜索(
/、:substitute等)内容会改变“上次使用的搜索内容”。(它保存在/寄存器中,用:echo @/可以输出它里面的内容) -
简单的文本变化可以通过
.重做。(它保存在.寄存器,用:echo @.可以输出它的内容)
而在你在函数中进行这些操作的时候,一切就会变得不同。因此你不能用 N/n 查找某个函数刚刚查找的内容,也不能重做函数中对文本的修改。
帮助文档::h function-search-undo。
进阶阅读
加入我们
可以协助我们核对翻译,或者从章节列表中认领章节进行翻译。
参考资料
未命名
如何将 Obsidian 与 Anki 同步? - 知乎
浏览 672 扫码 分享 2023-02-15 00:29:15
本文讨论如何在仅在 Obsidian 标题行加入一个
#flashcard的情况下就将 Obsidian 内的笔记标题及其相邻部分的内容同步至Anki中。
亮点:
-
- 只需要在标题行输入 #flashcard ,不需要复杂的语法,输入简便
-
- 修改 Obsidian 内的笔记,改动后的内容可以实时同步进 Anki,减少 Anki 内笔记的维护成本
先看效果图:

Obsidian 里的内容,标题行加入#flashcard

Anki 卡片中的预览图(正面为标题,反面为标题 + 内容)
下面开始讲述具体操作:
-
在 Obsidian 中安装 Obsidian_to_Anki 插件

打开设置

打开第三方插件并点击浏览

搜索插件名称Obsidian_to_Anki并安装
2. 在 Anki 中安装 AnkiConnect 并且在配置文件中将这一行
"http://localhost"
改为:
"http://localhost","app://obsidian.md"

在 ankiconnect 配置页面添加上述字符串
-
注意其中的半角逗号
,不能少
3. 在 Obsidian_to_Anki 的插件中的Note type table中自己所使用卡片类型一栏里插入正则表达式 -原文链接:
-
((?:[^\n][\n]?)+) #flashcard ?\n*((?:\n(?:^.{1,3}$|^.{4}(?<!<!--).*))+)

4. 在所需制作卡片的问题行的末尾处,加入#flashcard标签,该行则成为 Anki 卡片中的问题,紧挨着该行的下一段则成为问题答案,如:
How to sync Obsidian wih Anki? #flashcard
use the Obsidian_to_Anki addon

在标题行空格后加入#flashcard标签
5. 在_anki 正在运行的情况下_,点击插件的同步按钮之后,段落尾部会生成一段 ID 号码用于标记,而后卡片即同步进 Anki:
How to sync Obsidian wih Anki? #flashcard
use the Obsidian_to_Anki addon
ID-12341352345

添加完 #flashcard 标签后点击如图同步按钮即可将内容同步至 anki
6. 若要删除该问题,则需在该 ID 号码前手动键入DELETE,即:
How to sync Obsidian wih Anki? #flashcard
use the Obsidian_to_Anki addon
DELETE
ID-12341352345
而后点击同步,即可完成两个软件之间的笔记同步

在 ID 号前一行输入大写的 DELETE 而后同步,即可删除 Anki 中的卡片
7. 同步按钮的快捷键可以在设置中进行更改

在设置中的快捷键选项中设置同步的快捷键
未命名
[!Link]- Obsidian 学习地址
Obsidian牛掰却让新手头疼的笔记软件,这教程给你喂到嘴边,不会你找我退币_哔哩哔哩_bilibili
相关笔记 [[Obsidian牛掰却让新手头疼的笔记软件,这教程给你喂到嘴边,不会你找我退币|obsidian 内容点集锦]]
关于文内链接
-
链接到某一篇笔记:
[[ {笔记名} ]] -
链接到外部地址:
[ {关键词} ]( {url} ) -
链接到某篇笔记某个标题
[[ {笔记名}#{标题名} ]] -
链接到某篇笔记某个文本块
[[ {笔记名}#^{选择文本块} ]] -
指定显示文本
[[ {显示内容}|{替换文本} ]]
插件使用
[!EG] [[obsidian 与 anki 联用教程]]
[!EG] [[如何将Obsidian与Anki同步 知乎]]
图床搭建及说明
[!EG] [[README#关于本知识库所用图片|图床搭建说明]]
未命名
obsidian 内容点集锦
-
[3] 外观主题:卡片界面,配色,彩虹文件样式,文件图标,泡泡条,工具栏,callout 样式,程序块高亮样式,文字色块,ckecklist 样式
-
[0] 功能:一键记灵感/日记,自定义文件夹/文件排序,日历,简易 todo list,自定义按钮,悬浮窗,标签管理,最近使用文件,链接预览
[!download] 需要插件
Admonition,Calendar,Checklist,Commander,Customer File Explore sorting,Dataview,Easy Typing,Editing Toolbar,File Tree Alternative Plugin,Highlightr,Hover Editor,Iconize,Minimal Theme Settings,obsidian markmind,Paste URL into selection,QuickAdd,Recent Files,Style Settings,TagFolder,Templater,Convert url to preview (iframe), Tag Wrangler, Editor Syntax Highlight
该教程所需知道的常用操作
插件和主题下载需要梯子,没有梯子在我的视频的简介下自取插件下载后需要激活安装的插件在左下角的设置界面可以管理,大多数插件可以在这里设置
[!tip]
另外,插件命令可以 Ctrl+P 呼出命令面板,输入插件名,会出现该插件相应的命令例如 Checklist 插件,激活插件后没有显示,可以用这个方式
下载 AnuPpuccin 主题
[!info] 说明
该主题是后续很多外观样式的前置条件,需要下载激活后,后续的例如泡泡条,checklist 样式,卡片界面等才可以设置
[!download] 新主题下载激活如果有梯子的,自己下载,没有梯子打不开 obsidian 主题商店的,看下面的说明下载没有梯子的,在我的视频简介下载的文件里,把“主题”文件夹下的,AnuPpuccin 文件夹放入 obsidian 的主题文件夹(找到该文件夹的方式见下图)
安装插件
查看视频
案例:悬浮窗
一键呼出 <样式设置悬浮面板>
[!download] 需要插件
Hover Editor,Commander
代码块示例
[!download] 需要插件
Editor Syntax Highlight
1 | import pandas as pd |
1 | using System; |
CheckBox 示例样式
[!download] 需要插件
Style Settings
[!download] 需要下载主题
AnuPpuccin
[!info] 需要设置
打开插件 Style Settings 中的开关
-
[p] 点赞
-
[*] 收藏
:coin_bcoin: 投币 -
[>] Rescheduled
-
[<] Scheduled
-
[!] Important
-
[/] In Progress
-
[n] Note
-
[l] Location
-
[i] Information
-
[I] Idea
-
[S] Amount
-
[c] Con
-
[b] Bookmark
-
["] Quote
泡泡条示例样式
[!info] PS
与 CheckBox 设置相同
-
[0] 泡泡条示例样式 0 泡泡条示例样式 0 泡泡条示例样式 0 泡泡条示例样式 0
-
[1] 泡泡条示例样式 1 泡泡条示例样式 1 泡泡条示例样式 1
-
[2] 泡泡条示例样式 2 泡泡条示例样式 2
-
[3] 泡泡条示例样式 3
-
[4] 示例样式 4
-
[5] 样式 5
-
[6] 泡泡条示例样式 6
-
[7] 泡泡条示例样式 7 泡泡条示例样式 7
-
[8] 泡泡条示例样式 8 泡泡条示例样式 8 泡泡条示例样式 8
-
[9] 泡泡条示例样式 9 泡泡条示例样式 9 泡泡条示例样式 9 泡泡条示例样式 9
案例:一键记灵感/日记
[!download] 需要插件安装核心插件 日记,第三方插件 QuickAdd,Calendar
一键写灵感,一个灵感一篇笔记
1. 新建文件夹,用来存放 灵感/日记
[!info] 文件创建
设置完之后写的笔记会放在灵感 memo 文件夹下,如灵感 20231016. md 文件,该路径为 灵感/灵感memo ,路径第 4 步会用到
2. 设置笔记模板文件
[!example] 流程新建笔记 :luc_arrow_big_right: 放到指示文件位置 :luc_arrow_big_right: 开头输入
---会出现文档属性 :luc_arrow_big_right:created先用文本的格式,等输入{{date}}后再改成日期格式 :luc_arrow_big_right: 之后按示例完成设置,
[!info] 文件位置
文件存放路径为 obsidian使用相关/templater/灵感模板.md ,路径第 4 步会用到
[!info] 模板设置最终示例
3. 打开 QuickAdd 插件设置界面(设置界面,第三方插件下面找到对应插件)
4. 设置 QuickAdd-创建自定义动作
[!important] 重要记得点亮动作的黄色闪电
5. 设置创建的动作,点击动作后面的齿轮,进入设置界面
1 |
|
6. 使用方式
[!赞] 使用方式1:使用命令行按快捷键 Ctrl + P 呼出命令行,搜索之前设置的动作名称
inspire或者插件名quickadd,之后弹出第一个名为灵感标题窗口,该窗口输入笔记标题。按下确定后,弹出第二个窗口,这里输出的是笔记内容。
[!收藏] 使用方式2:设置快捷键设置 - 快捷键 - 搜索
inspire- 设置快捷键
一键写灵感,所有灵感都记在一篇笔记
1. 新建笔记,用来记录所有灵感
[!info] 文件创建
设置完之后写的笔记会添加到 灵感 文件夹下的 灵感散记.md 文件中,该路径为 灵感/灵感 散记.md ,路径第 4 步会用到
2 . 打开 QuickAdd 插件设置界面【同上】
3 . 设置 QuickAdd-创建自定义动作,【同上】,修改下动作名 2inspire
4 . 设置创建的动作,点击动作后面的齿轮,进入设置界面
1 | //这留1个空行 |
5. 使用方式【同上】
将日记内容添加到当天的日记文件中,我主要快速写待办用
1. 创建文件夹,用来存放日记,命名“日记”
[!info] 创建文件夹
文件夹路径 日记 ,路径第 3 步会用到
2. 设置笔记模板文件
[!example] 流程新建笔记 :luc_arrow_big_right: 放到指示文件位置 :luc_arrow_big_right: 开头输入
---会出现文档属性 :luc_arrow_big_right:created先用文本的格式,等输入{{date}}后再改成日期格式 :luc_arrow_big_right: 之后按示例完成设置,
[!info] 文件位置
文件存放路径为 obsidian使用相关/templater/日记模板.md ,路径第 3 步会用到
[!info] 模板设置最终示例
3. 设置日记插件
4. 设置 QuickAdd-创建自定义动作,【同上】,修改下动作名 todo
5 . 设置创建的动作,点击动作后面的齿轮,进入设置界面
1 | //```js quickadd |
6. 使用方式
[!important] 每天点击日历上当天日期,创建当天日记,然后使用方式【同上】
案例:自定义文件排序
[!download] 需要插件
Customer File Explore sorting
[!tip]
Shift + enter 可以在属性编辑输入多行
[!warning] 设置
这一案例的设置请查看视频
自定义按钮
[!download] 需要插件
Commander
标签管理
[!download] 需要插件
Tag Wrangler, TagFolder
最近使用文件
[!download] 需要插件
Recent Files
Ctrl + P 呼出命令面板,搜索 recent files:open,最近使用文件的按钮就会出现了
链接预览
[!download] 需要插件
Convert url to preview (iframe)
将链接粘贴到 obsidian
https://space.bilibili.com/2080168582
选择链接 Ctrl + P 呼出命令面板,搜索 convert
点击 Convert url to preview(iframe):URL to Preview/Iframe
自定义 callout 样式
[!good] 点赞
自定义 callout
[!download] 创建自定义 callout 设置示例
[!download] 使用示例说明
Ctrl + P ,搜索callout,选择Admonition:Insert Callout
卡片界面
[!download] 需要插件
Style Settings
[!download] 需要下载主题
AnuPpuccin
[!info] 设置
彩虹文件管理
[!download] 需要插件
Style Settings
[!download] 需要下载主题
AnuPpuccin
[!info] 设置
案例:文件/文件夹的 ICON 设置
[!download] 需要插件
Iconize
[!download] 安装 Icon 包有梯子的自己下载
没有梯子的,icon 包我放视频简介了,下面是安装方法
[!info] 使用方法1
右键文件/文件夹
[!info] 使用方法2:规则
显示 todo 任务汇总侧边栏
[!download] 需要插件
Checklist
文字色块
你们的一键三连,是我创作的动力~
你们的一键三连,是我创作的动力!
工具栏
下划线
[!download] 需要插件
Editing Toolbar
[!info] 显示工具栏
Ctrl+ P,搜索
设置搜索框
[!download] 需要插件
Settings search
当安装了很多插件后,有时候不知道某些设置在哪个插件,或者哪里设置,有了这个就会方便很多
比如: 显示行号
多级标题自定义颜色
未命名
本文由 简悦 SimpRead 转码, 原文地址 www.jianshu.com
Markdown 箭头的输入方法汇总
普通箭头
| 箭头形状 | MarkDown |
|---|---|
| $\uparrow$ | $\uparrow$ |
| $\Uparrow$ | $\Uparrow$ |
| $\downarrow$ | $\downarrow$ |
| $\Downarrow$ | $\Downarrow$ |
| $\leftarrow$ | $\leftarrow$ |
| $\Leftarrow$ | $\Leftarrow$ |
| $\rightarrow$ | $\rightarrow$ |
| $\Rightarrow$ | $\Rightarrow$ |
| $\updownarrow$ | $\updownarrow$ |
| $\Updownarrow$ | $\Updownarrow$ |
| $\leftrightarrow$ | $\leftrightarrow$ |
| $\Leftrightarrow$ | $\Leftrightarrow$ |
长箭头
| 箭头形状 | MarkDown |
|---|---|
| $\longleftarrow$ | $\longleftarrow$ |
| $\Longleftarrow$ | $\Longleftarrow$ |
| $\longrightarrow$ | $\longrightarrow$ |
| $\Longrightarrow$ | $\Longrightarrow$ |
| $\longleftrightarrow$ | $\longleftrightarrow$ |
| $\Longleftrightarrow$ | $\Longleftrightarrow$ |
其他箭头
| 箭头形状 | MarkDown |
|---|---|
| $\twoheadrightarrow$ | $\twoheadrightarrow$ |
| $\rightarrowtail$ | $\rightarrowtail$ |
| $\looparrowright$ | $\looparrowright$ |
| $\curvearrowright$ | $\curvearrowright$ |
| $\circlearrowright$ | $\circlearrowright$ |
| $\Rsh$ | $\Rsh$ |
| $\multimap$ | $\multimap$ |
| $\leftrightsquigarrow$ | $\leftrightsquigarrow$ |
| $\rightsquigarrow$ | $\rightsquigarrow$ |
| $\leadsto$ | $\leadsto$ |
| $\nearrow$ | $\nearrow$ |
| $\searrow$ | $\searrow$ |
| $\swarrow$ | $\swarrow$ |
| $\nwarrow$ | $\nwarrow$ |
| $\nleftarrow$ | $\nleftarrow$ |
| $\nLeftarrow$ | $\nLeftarrow$ |
| $\nrightarrow$ | $\nrightarrow$ |
| $\nRightarrow$ | $\nRightarrow$ |
| $\nleftrightarrow$ | $\nleftrightarrow$ |
| $\nLeftrightarrow$ | $\nLeftrightarrow$ |
| $\dashrightarrow$ | $\dashrightarrow$ |
| $\dashleftarrow$ | $\dashleftarrow$ |
| $\leftleftarrows$ | $\leftleftarrows$ |
| $\leftrightarrows$ | $\leftrightarrows$ |
| $\Lleftarrow$ | $\Lleftarrow$ |
| $\twoheadleftarrow$ | $\twoheadleftarrow$ |
| $\leftarrowtail$ | $\leftarrowtail$ |
| $\looparrowleft$ | $\looparrowleft$ |
| $\curvearrowleft$ | $\curvearrowleft$ |
| $\circlearrowleft$ | $\circlearrowleft$ |
| $\Lsh$ | $\Lsh$ |
| $\mapsto$ | $\mapsto$ |
| $\hookleftarrow$ | $\hookleftarrow$ |
| $\hookrightarrow$ | $\hookrightarrow$ |
| $\upharpoonright$ | $\upharpoonright$ |
| $\upharpoonleft$ | $\upharpoonleft$ |
| $\downharpoonright$ | $\downharpoonright$ |
| $\downharpoonleft$ | $\downharpoonleft$ |
| $\leftharpoonup$ | $\leftharpoonup$ |
| $\rightharpoonup$ | $\rightharpoonup$ |
| $\rightharpoondown$ | $\rightharpoondown$ |
| $\leftharpoondown$ | $\leftharpoondown$ |
| $\upuparrows$ | $\upuparrows$ |
| $\downdownarrows$ | $\downdownarrows$ |
| $\rightrightarrows$ | $\rightrightarrows$ |
| $\rightleftarrows$ | $\rightleftarrows$ |
| $\leftleftarrows$ | $\leftleftarrows$ |
| $\leftrightarrows$ | $\leftrightarrows$ |
| $\rightleftharpoons$ | $\rightleftharpoons$ |
| $\leftrightharpoons$ | $\leftrightharpoons$ |
关于在本知识库中使用的输入方式
通过插件:
[!info]- EasyTyping
一个可以将一端文字输入映射为另一端文字的快速打字插件
对箭头符号进行了一些简单映射:
[!tip] 箭头映射方式
\->$\rightarrow$ $\rightarrow$\=>$\rightarrow$ $\Rightarrow$\-->$\rightarrow$ $\longrightarrow$\==>$\rightarrow$ $\Longrightarrow$
assdfasdfasf
fassdfasdf
asdfassdfasdf
$\leftarrow$
<=
$\leftarrow$
未命名
本文由 简悦 SimpRead 转码, 原文地址 zhuanlan.zhihu.com
update: 现在 obsidian 已经有软件内就可用的插件了,更推荐这种做法,而不是导出到 anki 软件
原文: 与 Anki 联动的最佳实践? · Discussion #21 · obsidianzh/forum
我们经常希望在 obsidian 记录的笔记能够方便地转换到 anki 中生成记忆卡片,具体要怎么做呢?
最终效果
假设你在 obsidian 的笔记长这样
1 | - Question1 |
打开 anki 和 obsidian,点击 obsidian 左侧菜单栏后

你的 anki 会多出如下卡片

安装
-
anki 安装并配置 anki connector 插件
-
Open the
Install Add-ondialog by selectingTools|Add-ons|Get Add-ons...in Anki. -
Input 2055492159 into the text box labeled
Codeand press theOKbutton to proceed. -
Restart Anki when prompted to do so in order to complete the installation of AnkiConnect.
-
In Anki, navigate to Tools->Addons->AnkiConnect->Config, and change it to look like this:
1 | { |
-
obsidian 安装并配置 Obsidian_to_anki 插件
-
在
Community plugins中搜索并安装Obsidian_to_anki,如果无法加载社区插件,请见这里 -
在
Third-party plugins中开启Obsidian_to_anki插件,重启 obsidian -
在 Plugin Options 中找到 obsidian_to_anki 的配置,在
Note Type Table的Basic卡 对应的Custom Regexp一栏中添加下面的内容 -
^\- ((?:.+\n)*?)\n*[\t ]+((\- )?.+(?:\n(?:^.{4}(?<!<!--)(?<!\- ..).*))*)(?:\n^$)? -
这个 “代码” 参考
-
你可以查看插件的 wiki 英文帮助文档熟悉更多关于这个插件的配置
生成 / 更新卡片
配置完成后!随便找一个文档,输入上文中的
1 | - 问题1 |
然后生成 / 更新卡片,有两种方法
-
点击 obsidian 左侧栏中的 anki 图标,这可能要花一点时间,注意软件右上角的提示
-
ctrl + P呼出 obsidian 的命令框,输入Obsidian_to_Anki: Scan Vault,然后Enter执行即可。
常见问题
-
-跟 markdown 的列表冲突了? -
你可以用
*来作为普通的列表,* [ ]来标识 todo -
如何删除卡片?
-
DELETE加在ID:xxxx的上方 -
如何避免符合上述格式的笔记被添加到 anki 中?
-
把内容包含在代码块里头,即在内容的前后添加一行 ```
-
obsidian 笔记有点多,如何折腾?
-
ctrl + p,然后在命令框中输入Fold all headings and lists折叠,Unfold all headings and lists取消折叠 -
和 flashcard 插件的区别?
-
flashcard:
-
支持当前页笔记 导入到 anki(但不能一次扫描所有文件)
-
支持两种格式的笔记(按下面的写法,就能够转换成 anki 卡片)
1 | # Question #card |
-
Obsidian_to_anki
-
支持扫描所有文件导入到 anki
-
支持多种格式的笔记(需要多一步配置,如果懂正则表达式的话,还可以自己定义格式)
-
上面的
Question::Card -
填空题
中间是一个{填空}题 -
其他格式(就不一一举例了,具体见: https://github.com/Pseudonium/Obsidian_to_Anki/wiki/Regex)
-
总之: 如果有上面自定义格式 + 扫描所有文件的刚需,可以用 obsidian_to_anki 插件(目前我在用的,我还写了一个自定义的笔记格式,见: https://github.com/Pseudonium/Obsidian_to_Anki/issues/192#issuecomment-756303155)
-
有视频教程吗?
-
flashcard 推荐新手使用
-
B 站上应该有搬运的
更多格式
如果觉得上面的 笔记转 anki 格式 还不满足你的要求,例如你希望有挖空 / 填空题卡片,或者标题为问题,正文为内容,
你可以在这里查看所有 格式(regex)列表: https://github.com/Pseudonium/Obsidian_to_Anki/wiki/Regex
你需要做的事情就是复制链接中提供的 代码(一串字符) 到 插件设置里头的 custom regex 中
举例:
-
标题格式
^#+(.+)#card\n*((?:\n(?:^[^\n#].{0,2}$|^[^\n#].{3}(?<!<!--).*))+) -
双冒号格式
^(.*[^\n:]{1}):{2}([^\n:]{1}.*)

几个例子
1 | **RemNote single line style** |
已知问题
-
Add context / tag 在 custom regex 中不可用
未命名
未命名
PowerToys 按键映射以及其他(如窗口顶置 等)各种有趣功能的软件
未命名
') .replace(DOUBLE_SPACE_REGEXP, ' '); return '\n' + '\n' + '\n' + '\n' + '
' + body + '\n' + '\n' + '\n' } /** * Module exports. * @public */ var finalhandler_1 = finalhandler; /** * Create a function to handle the final response. * * @param {Request} req * @param {Response} res * @param {Object} [options] * @return {Function} * @public */ function finalhandler (req, res, options) { var opts = options || {}; // get environment var env = opts.env || process.env.NODE_ENV || 'development'; // get error callback var onerror = opts.onerror; return function (err) { var headers; var msg; var status; // ignore 404 on in-flight response if (!err && headersSent$1(res)) { debug$7('cannot 404 after headers sent'); return } // unhandled error if (err) { // respect status code from error status = getErrorStatusCode(err); if (status === undefined) { // fallback to status code on response status = getResponseStatusCode(res); } else { // respect headers from error headers = getErrorHeaders(err); } // get error message msg = getErrorMessage(err, status, env); } else { // not found status = 404; msg = 'Cannot ' + req.method + ' ' + encodeUrl$3(getResourceName(req)); } debug$7('default %s', status); // schedule onerror callback if (err && onerror) { defer(onerror, err, req, res); } // cannot actually respond if (headersSent$1(res)) { debug$7('cannot %d after headers sent', status); req.socket.destroy(); return } // send response send$4(req, res, status, headers, msg); } } /** * Get headers from Error object. * * @param {Error} err * @return {object} * @private */ function getErrorHeaders (err) { if (!err.headers || typeof err.headers !== 'object') { return undefined } var headers = Object.create(null); var keys = Object.keys(err.headers); for (var i = 0; i < keys.length; i++) { var key = keys[i]; headers[key] = err.headers[key]; } return headers } /** * Get message from Error object, fallback to status message. * * @param {Error} err * @param {number} status * @param {string} env * @return {string} * @private */ function getErrorMessage (err, status, env) { var msg; if (env !== 'production') { // use err.stack, which typically includes err.message msg = err.stack; // fallback to err.toString() when possible if (!msg && typeof err.toString === 'function') { msg = err.toString(); } } return msg || statuses$2[status] } /** * Get status code from Error object. * * @param {Error} err * @return {number} * @private */ function getErrorStatusCode (err) { // check err.status if (typeof err.status === 'number' && err.status >= 400 && err.status < 600) { return err.status } // check err.statusCode if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 600) { return err.statusCode } return undefined } /** * Get resource name for the request. * * This is typically just the original pathname of the request * but will fallback to "resource" is that cannot be determined. * * @param {IncomingMessage} req * @return {string} * @private */ function getResourceName (req) { try { return parseUrl$3.original(req).pathname } catch (e) { return 'resource' } } /** * Get status code from response. * * @param {OutgoingMessage} res * @return {number} * @private */ function getResponseStatusCode (res) { var status = res.statusCode; // default status code to 500 if outside valid range if (typeof status !== 'number' || status < 400 || status > 599) { status = 500; } return status } /** * Determine if the response headers have been sent. * * @param {object} res * @returns {boolean} * @private */ function headersSent$1 (res) { return typeof res.headersSent !== 'boolean' ? Boolean(res._header) : res.headersSent } /** * Send response. * * @param {IncomingMessage} req * @param {OutgoingMessage} res * @param {number} status * @param {object} headers * @param {string} message * @private */ function send$4 (req, res, status, headers, message) { function write () { // response body var body = createHtmlDocument$2(message); // response status res.statusCode = status; res.statusMessage = statuses$2[status]; // response headers setHeaders$1(res, headers); // security headers res.setHeader('Content-Security-Policy', "default-src 'none'"); res.setHeader('X-Content-Type-Options', 'nosniff'); // standard headers res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8')); if (req.method === 'HEAD') { res.end(); return } res.end(body, 'utf8'); } if (isFinished(req)) { write(); return } // unpipe everything from the request unpipe(req); // flush the request onFinished$2(req, write); req.resume(); } /** * Set response headers from an object. * * @param {OutgoingMessage} res * @param {object} headers * @private */ function setHeaders$1 (res, headers) { if (!headers) { return } var keys = Object.keys(headers); for (var i = 0; i < keys.length; i++) { var key = keys[i]; res.setHeader(key, headers[key]); } } var router$1 = {exports: {}}; var src$1 = {exports: {}}; var browser$1 = {exports: {}}; var debug$6 = {exports: {}}; /** * Helpers. */ var s$2 = 1000; var m$2 = s$2 * 60; var h$2 = m$2 * 60; var d$2 = h$2 * 24; var y$2 = d$2 * 365.25; /** * Parse or format the given `val`. * * Options: * * - `long` verbose formatting [false] * * @param {String|Number} val * @param {Object} [options] * @throws {Error} throw an error if val is not a non-empty string or a number * @return {String|Number} * @api public */ var ms$3 = function(val, options) { options = options || {}; var type = typeof val; if (type === 'string' && val.length > 0) { return parse$b(val); } else if (type === 'number' && isNaN(val) === false) { return options.long ? fmtLong$2(val) : fmtShort$2(val); } throw new Error( 'val is not a non-empty string or a valid number. val=' + JSON.stringify(val) ); }; /** * Parse the given `str` and return milliseconds. * * @param {String} str * @return {Number} * @api private */ function parse$b(str) { str = String(str); if (str.length > 100) { return; } var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( str ); if (!match) { return; } var n = parseFloat(match[1]); var type = (match[2] || 'ms').toLowerCase(); switch (type) { case 'years': case 'year': case 'yrs': case 'yr': case 'y': return n * y$2; case 'days': case 'day': case 'd': return n * d$2; case 'hours': case 'hour': case 'hrs': case 'hr': case 'h': return n * h$2; case 'minutes': case 'minute': case 'mins': case 'min': case 'm': return n * m$2; case 'seconds': case 'second': case 'secs': case 'sec': case 's': return n * s$2; case 'milliseconds': case 'millisecond': case 'msecs': case 'msec': case 'ms': return n; default: return undefined; } } /** * Short format for `ms`. * * @param {Number} ms * @return {String} * @api private */ function fmtShort$2(ms) { if (ms >= d$2) { return Math.round(ms / d$2) + 'd'; } if (ms >= h$2) { return Math.round(ms / h$2) + 'h'; } if (ms >= m$2) { return Math.round(ms / m$2) + 'm'; } if (ms >= s$2) { return Math.round(ms / s$2) + 's'; } return ms + 'ms'; } /** * Long format for `ms`. * * @param {Number} ms * @return {String} * @api private */ function fmtLong$2(ms) { return plural$2(ms, d$2, 'day') || plural$2(ms, h$2, 'hour') || plural$2(ms, m$2, 'minute') || plural$2(ms, s$2, 'second') || ms + ' ms'; } /** * Pluralization helper. */ function plural$2(ms, n, name) { if (ms < n) { return; } if (ms < n * 1.5) { return Math.floor(ms / n) + ' ' + name; } return Math.ceil(ms / n) + ' ' + name + 's'; } (function (module, exports) { /** * This is the common logic for both the Node.js and web browser * implementations of `debug()`. * * Expose `debug()` as the module. */ exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; exports.humanize = ms$3; /** * The currently active debug mode names, and names to skip. */ exports.names = []; exports.skips = []; /** * Map of special "%n" handling functions, for the debug "format" argument. * * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". */ exports.formatters = {}; /** * Previous log timestamp. */ var prevTime; /** * Select a color. * @param {String} namespace * @return {Number} * @api private */ function selectColor(namespace) { var hash = 0, i; for (i in namespace) { hash = ((hash << 5) - hash) + namespace.charCodeAt(i); hash |= 0; // Convert to 32bit integer } return exports.colors[Math.abs(hash) % exports.colors.length]; } /** * Create a debugger with the given `namespace`. * * @param {String} namespace * @return {Function} * @api public */ function createDebug(namespace) { function debug() { // disabled? if (!debug.enabled) return; var self = debug; // set `diff` timestamp var curr = +new Date(); var ms = curr - (prevTime || curr); self.diff = ms; self.prev = prevTime; self.curr = curr; prevTime = curr; // turn the `arguments` into a proper Array var args = new Array(arguments.length); for (var i = 0; i < args.length; i++) { args[i] = arguments[i]; } args[0] = exports.coerce(args[0]); if ('string' !== typeof args[0]) { // anything else let's inspect with %O args.unshift('%O'); } // apply any `formatters` transformations var index = 0; args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { // if we encounter an escaped % then don't increase the array index if (match === '%%') return match; index++; var formatter = exports.formatters[format]; if ('function' === typeof formatter) { var val = args[index]; match = formatter.call(self, val); // now we need to remove `args[index]` since it's inlined in the `format` args.splice(index, 1); index--; } return match; }); // apply env-specific formatting (colors, etc.) exports.formatArgs.call(self, args); var logFn = debug.log || exports.log || console.log.bind(console); logFn.apply(self, args); } debug.namespace = namespace; debug.enabled = exports.enabled(namespace); debug.useColors = exports.useColors(); debug.color = selectColor(namespace); // env-specific initialization logic for debug instances if ('function' === typeof exports.init) { exports.init(debug); } return debug; } /** * Enables a debug mode by namespaces. This can include modes * separated by a colon and wildcards. * * @param {String} namespaces * @api public */ function enable(namespaces) { exports.save(namespaces); exports.names = []; exports.skips = []; var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); var len = split.length; for (var i = 0; i < len; i++) { if (!split[i]) continue; // ignore empty strings namespaces = split[i].replace(/\*/g, '.*?'); if (namespaces[0] === '-') { exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); } else { exports.names.push(new RegExp('^' + namespaces + '$')); } } } /** * Disable debug output. * * @api public */ function disable() { exports.enable(''); } /** * Returns true if the given mode name is enabled, false otherwise. * * @param {String} name * @return {Boolean} * @api public */ function enabled(name) { var i, len; for (i = 0, len = exports.skips.length; i < len; i++) { if (exports.skips[i].test(name)) { return false; } } for (i = 0, len = exports.names.length; i < len; i++) { if (exports.names[i].test(name)) { return true; } } return false; } /** * Coerce `val`. * * @param {Mixed} val * @return {Mixed} * @api private */ function coerce(val) { if (val instanceof Error) return val.stack || val.message; return val; } }(debug$6, debug$6.exports)); /** * This is the web browser implementation of `debug()`. * * Expose `debug()` as the module. */ (function (module, exports) { exports = module.exports = debug$6.exports; exports.log = log; exports.formatArgs = formatArgs; exports.save = save; exports.load = load; exports.useColors = useColors; exports.storage = 'undefined' != typeof chrome && 'undefined' != typeof chrome.storage ? chrome.storage.local : localstorage(); /** * Colors. */ exports.colors = [ 'lightseagreen', 'forestgreen', 'goldenrod', 'dodgerblue', 'darkorchid', 'crimson' ]; /** * Currently only WebKit-based Web Inspectors, Firefox >= v31, * and the Firebug extension (any Firefox version) are known * to support "%c" CSS customizations. * * TODO: add a `localStorage` variable to explicitly enable/disable colors */ function useColors() { // NB: In an Electron preload script, document will be defined but not fully // initialized. Since we know we're in Chrome, we'll just detect this case // explicitly if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { return true; } // is webkit? http://stackoverflow.com/a/16459606/376773 // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || // is firebug? http://stackoverflow.com/a/398120/376773 (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || // is firefox >= v31? // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || // double check webkit in userAgent just in case we are in a worker (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); } /** * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. */ exports.formatters.j = function(v) { try { return JSON.stringify(v); } catch (err) { return '[UnexpectedJSONParseError]: ' + err.message; } }; /** * Colorize log arguments if enabled. * * @api public */ function formatArgs(args) { var useColors = this.useColors; args[0] = (useColors ? '%c' : '') + this.namespace + (useColors ? ' %c' : ' ') + args[0] + (useColors ? '%c ' : ' ') + '+' + exports.humanize(this.diff); if (!useColors) return; var c = 'color: ' + this.color; args.splice(1, 0, c, 'color: inherit'); // the final "%c" is somewhat tricky, because there could be other // arguments passed either before or after the %c, so we need to // figure out the correct index to insert the CSS into var index = 0; var lastC = 0; args[0].replace(/%[a-zA-Z%]/g, function(match) { if ('%%' === match) return; index++; if ('%c' === match) { // we only are interested in the *last* %c // (the user may have provided their own) lastC = index; } }); args.splice(lastC, 0, c); } /** * Invokes `console.log()` when available. * No-op when `console.log` is not a "function". * * @api public */ function log() { // this hackery is required for IE8/9, where // the `console.log` function doesn't have 'apply' return 'object' === typeof console && console.log && Function.prototype.apply.call(console.log, console, arguments); } /** * Save `namespaces`. * * @param {String} namespaces * @api private */ function save(namespaces) { try { if (null == namespaces) { exports.storage.removeItem('debug'); } else { exports.storage.debug = namespaces; } } catch(e) {} } /** * Load `namespaces`. * * @return {String} returns the previously persisted debug modes * @api private */ function load() { var r; try { r = exports.storage.debug; } catch(e) {} // If debug isn't set in LS, and we're in Electron, try to load $DEBUG if (!r && typeof process !== 'undefined' && 'env' in process) { r = process.env.DEBUG; } return r; } /** * Enable namespaces listed in `localStorage.debug` initially. */ exports.enable(load()); /** * Localstorage attempts to return the localstorage. * * This is necessary because safari throws * when a user disables cookies/localstorage * and you attempt to access it. * * @return {LocalStorage} * @api private */ function localstorage() { try { return window.localStorage; } catch (e) {} } }(browser$1, browser$1.exports)); var node$1 = {exports: {}}; /** * Module dependencies. */ (function (module, exports) { var tty = require$$0__default$1['default']; var util = require$$1__default['default']; /** * This is the Node.js implementation of `debug()`. * * Expose `debug()` as the module. */ exports = module.exports = debug$6.exports; exports.init = init; exports.log = log; exports.formatArgs = formatArgs; exports.save = save; exports.load = load; exports.useColors = useColors; /** * Colors. */ exports.colors = [6, 2, 3, 4, 5, 1]; /** * Build up the default `inspectOpts` object from the environment variables. * * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js */ exports.inspectOpts = Object.keys(process.env).filter(function (key) { return /^debug_/i.test(key); }).reduce(function (obj, key) { // camel-case var prop = key .substring(6) .toLowerCase() .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); // coerce string value into JS value var val = process.env[key]; if (/^(yes|on|true|enabled)$/i.test(val)) val = true; else if (/^(no|off|false|disabled)$/i.test(val)) val = false; else if (val === 'null') val = null; else val = Number(val); obj[prop] = val; return obj; }, {}); /** * The file descriptor to write the `debug()` calls to. * Set the `DEBUG_FD` env variable to override with another value. i.e.: * * $ DEBUG_FD=3 node script.js 3>debug.log */ var fd = parseInt(process.env.DEBUG_FD, 10) || 2; if (1 !== fd && 2 !== fd) { util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')(); } var stream = 1 === fd ? process.stdout : 2 === fd ? process.stderr : createWritableStdioStream(fd); /** * Is stdout a TTY? Colored output is enabled when `true`. */ function useColors() { return 'colors' in exports.inspectOpts ? Boolean(exports.inspectOpts.colors) : tty.isatty(fd); } /** * Map %o to `util.inspect()`, all on a single line. */ exports.formatters.o = function(v) { this.inspectOpts.colors = this.useColors; return util.inspect(v, this.inspectOpts) .split('\n').map(function(str) { return str.trim() }).join(' '); }; /** * Map %o to `util.inspect()`, allowing multiple lines if needed. */ exports.formatters.O = function(v) { this.inspectOpts.colors = this.useColors; return util.inspect(v, this.inspectOpts); }; /** * Adds ANSI color escape codes if enabled. * * @api public */ function formatArgs(args) { var name = this.namespace; var useColors = this.useColors; if (useColors) { var c = this.color; var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; args[0] = prefix + args[0].split('\n').join('\n' + prefix); args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); } else { args[0] = new Date().toUTCString() + ' ' + name + ' ' + args[0]; } } /** * Invokes `util.format()` with the specified arguments and writes to `stream`. */ function log() { return stream.write(util.format.apply(util, arguments) + '\n'); } /** * Save `namespaces`. * * @param {String} namespaces * @api private */ function save(namespaces) { if (null == namespaces) { // If you set a process.env field to null or undefined, it gets cast to the // string 'null' or 'undefined'. Just delete instead. delete process.env.DEBUG; } else { process.env.DEBUG = namespaces; } } /** * Load `namespaces`. * * @return {String} returns the previously persisted debug modes * @api private */ function load() { return process.env.DEBUG; } /** * Copied from `node/src/node.js`. * * XXX: It's lame that node doesn't expose this API out-of-the-box. It also * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. */ function createWritableStdioStream (fd) { var stream; var tty_wrap = process.binding('tty_wrap'); // Note stream._type is used for test-module-load-list.js switch (tty_wrap.guessHandleType(fd)) { case 'TTY': stream = new tty.WriteStream(fd); stream._type = 'tty'; // Hack to have stream not keep the event loop alive. // See https://github.com/joyent/node/issues/1726 if (stream._handle && stream._handle.unref) { stream._handle.unref(); } break; case 'FILE': var fs = require$$3__default['default']; stream = new fs.SyncWriteStream(fd, { autoClose: false }); stream._type = 'fs'; break; case 'PIPE': case 'TCP': var net = require$$4__default['default']; stream = new net.Socket({ fd: fd, readable: false, writable: true }); // FIXME Should probably have an option in net.Socket to create a // stream from an existing fd which is writable only. But for now // we'll just add this hack and set the `readable` member to false. // Test: ./node test/fixtures/echo.js < /etc/passwd stream.readable = false; stream.read = null; stream._type = 'pipe'; // FIXME Hack to have stream not keep the event loop alive. // See https://github.com/joyent/node/issues/1726 if (stream._handle && stream._handle.unref) { stream._handle.unref(); } break; default: // Probably an error on in uv_guess_handle() throw new Error('Implement me. Unknown stream file type!'); } // For supporting legacy API we put the FD here. stream.fd = fd; stream._isStdio = true; return stream; } /** * Init logic for `debug` instances. * * Create a new `inspectOpts` object in case `useColors` is set * differently for a particular `debug` instance. */ function init (debug) { debug.inspectOpts = {}; var keys = Object.keys(exports.inspectOpts); for (var i = 0; i < keys.length; i++) { debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; } } /** * Enable namespaces listed in `process.env.DEBUG` initially. */ exports.enable(load()); }(node$1, node$1.exports)); /** * Detect Electron renderer process, which is node, but we should * treat as a browser. */ if (typeof process !== 'undefined' && process.type === 'renderer') { src$1.exports = browser$1.exports; } else { src$1.exports = node$1.exports; } /** * Expose `arrayFlatten`. */ var arrayFlatten_1 = arrayFlatten; /** * Recursive flatten function with depth. * * @param {Array} array * @param {Array} result * @param {Number} depth * @return {Array} */ function flattenWithDepth (array, result, depth) { for (var i = 0; i < array.length; i++) { var value = array[i]; if (depth > 0 && Array.isArray(value)) { flattenWithDepth(value, result, depth - 1); } else { result.push(value); } } return result } /** * Recursive flatten function. Omitting depth is slightly faster. * * @param {Array} array * @param {Array} result * @return {Array} */ function flattenForever (array, result) { for (var i = 0; i < array.length; i++) { var value = array[i]; if (Array.isArray(value)) { flattenForever(value, result); } else { result.push(value); } } return result } /** * Flatten an array, with the ability to define a depth. * * @param {Array} array * @param {Number} depth * @return {Array} */ function arrayFlatten (array, depth) { if (depth == null) { return flattenForever(array, []) } return flattenWithDepth(array, [], depth) } /** * Expose `pathtoRegexp`. */ var pathToRegexp = pathtoRegexp; /** * Match matching groups in a regular expression. */ var MATCHING_GROUP_REGEXP = /\((?!\?)/g; /** * Normalize the given path string, * returning a regular expression. * * An empty array should be passed, * which will contain the placeholder * key names. For example "/user/:id" will * then contain ["id"]. * * @param {String|RegExp|Array} path * @param {Array} keys * @param {Object} options * @return {RegExp} * @api private */ function pathtoRegexp(path, keys, options) { options = options || {}; keys = keys || []; var strict = options.strict; var end = options.end !== false; var flags = options.sensitive ? '' : 'i'; var extraOffset = 0; var keysOffset = keys.length; var i = 0; var name = 0; var m; if (path instanceof RegExp) { while (m = MATCHING_GROUP_REGEXP.exec(path.source)) { keys.push({ name: name++, optional: false, offset: m.index }); } return path; } if (Array.isArray(path)) { // Map array parts into regexps and return their source. We also pass // the same keys and options instance into every generation to get // consistent matching groups before we join the sources together. path = path.map(function (value) { return pathtoRegexp(value, keys, options).source; }); return new RegExp('(?:' + path.join('|') + ')', flags); } path = ('^' + path + (strict ? '' : path[path.length - 1] === '/' ? '?' : '/?')) .replace(/\/\(/g, '/(?:') .replace(/([\/\.])/g, '\\$1') .replace(/(\\\/)?(\\\.)?:(\w+)(\(.*?\))?(\*)?(\?)?/g, function (match, slash, format, key, capture, star, optional, offset) { slash = slash || ''; format = format || ''; capture = capture || '([^\\/' + format + ']+?)'; optional = optional || ''; keys.push({ name: key, optional: !!optional, offset: offset + extraOffset }); var result = '' + (optional ? '' : slash) + '(?:' + format + (optional ? slash : '') + capture + (star ? '((?:[\\/' + format + '].+?)?)' : '') + ')' + optional; extraOffset += result.length - match.length; return result; }) .replace(/\*/g, function (star, index) { var len = keys.length; while (len-- > keysOffset && keys[len].offset > index) { keys[len].offset += 3; // Replacement length minus asterisk length. } return '(.*)'; }); // This is a workaround for handling unnamed matching groups. while (m = MATCHING_GROUP_REGEXP.exec(path)) { var escapeCount = 0; var index = m.index; while (path.charAt(--index) === '\\') { escapeCount++; } // It's possible to escape the bracket. if (escapeCount % 2 === 1) { continue; } if (keysOffset + i === keys.length || keys[keysOffset + i].offset > m.index) { keys.splice(keysOffset + i, 0, { name: name++, // Unnamed matching groups must be consistently linear. optional: false, offset: m.index }); } i++; } // If the path is non-ending, match until the end or a slash. path += (end ? '$' : (path[path.length - 1] === '/' ? '' : '(?=\\/|$)')); return new RegExp(path, flags); } /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. * @private */ var pathRegexp = pathToRegexp; var debug$5 = src$1.exports('express:router:layer'); /** * Module variables. * @private */ var hasOwnProperty = Object.prototype.hasOwnProperty; /** * Module exports. * @public */ var layer = Layer$2; function Layer$2(path, options, fn) { if (!(this instanceof Layer$2)) { return new Layer$2(path, options, fn); } debug$5('new %o', path); var opts = options || {}; this.handle = fn; this.name = fn.name || '
' + body + '\n' + '\n' + '\n' } /** * decodeURIComponent. * * Allows V8 to only deoptimize this fn instead of all * of send(). * * @param {String} path * @api private */ function decode$1 (path) { try { return decodeURIComponent(path) } catch (err) { return -1 } } /** * Get the header names on a respnse. * * @param {object} res * @returns {array[string]} * @private */ function getHeaderNames (res) { return typeof res.getHeaderNames !== 'function' ? Object.keys(res._headers || {}) : res.getHeaderNames() } /** * Determine if emitter has listeners of a given type. * * The way to do this check is done three different ways in Node.js >= 0.8 * so this consolidates them into a minimal set using instance methods. * * @param {EventEmitter} emitter * @param {string} type * @returns {boolean} * @private */ function hasListeners (emitter, type) { var count = typeof emitter.listenerCount !== 'function' ? emitter.listeners(type).length : emitter.listenerCount(type); return count > 0 } /** * Determine if the response headers have been sent. * * @param {object} res * @returns {boolean} * @private */ function headersSent (res) { return typeof res.headersSent !== 'boolean' ? Boolean(res._header) : res.headersSent } /** * Normalize the index option into an array. * * @param {boolean|string|array} val * @param {string} name * @private */ function normalizeList (val, name) { var list = [].concat(val || []); for (var i = 0; i < list.length; i++) { if (typeof list[i] !== 'string') { throw new TypeError(name + ' must be array of strings or false') } } return list } /** * Parse an HTTP Date into a number. * * @param {string} date * @private */ function parseHttpDate (date) { var timestamp = date && Date.parse(date); return typeof timestamp === 'number' ? timestamp : NaN } /** * Parse a HTTP token list. * * @param {string} str * @private */ function parseTokenList (str) { var end = 0; var list = []; var start = 0; // gather tokens for (var i = 0, len = str.length; i < len; i++) { switch (str.charCodeAt(i)) { case 0x20: /* */ if (start === end) { start = end = i + 1; } break case 0x2c: /* , */ list.push(str.substring(start, end)); start = end = i + 1; break default: end = i + 1; break } } // final token list.push(str.substring(start, end)); return list } /** * Set an object of headers on a response. * * @param {object} res * @param {object} headers * @private */ function setHeaders (res, headers) { var keys = Object.keys(headers); for (var i = 0; i < keys.length; i++) { var key = keys[i]; res.setHeader(key, headers[key]); } } var proxyAddr = {exports: {}}; /*! * forwarded * Copyright(c) 2014-2017 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ var forwarded_1 = forwarded$1; /** * Get all addresses in the request, using the `X-Forwarded-For` header. * * @param {object} req * @return {array} * @public */ function forwarded$1 (req) { if (!req) { throw new TypeError('argument req is required') } // simple header parsing var proxyAddrs = parse$7(req.headers['x-forwarded-for'] || ''); var socketAddr = getSocketAddr(req); var addrs = [socketAddr].concat(proxyAddrs); // return all addresses return addrs } /** * Get the socket address for a request. * * @param {object} req * @return {string} * @private */ function getSocketAddr (req) { return req.socket ? req.socket.remoteAddress : req.connection.remoteAddress } /** * Parse the X-Forwarded-For header. * * @param {string} header * @private */ function parse$7 (header) { var end = header.length; var list = []; var start = header.length; // gather addresses, backwards for (var i = header.length - 1; i >= 0; i--) { switch (header.charCodeAt(i)) { case 0x20: /* */ if (start === end) { start = end = i; } break case 0x2c: /* , */ if (start !== end) { list.push(header.substring(start, end)); } start = end = i; break default: start = i; break } } // final address if (start !== end) { list.push(header.substring(start, end)); } return list } var ipaddr$1 = {exports: {}}; (function (module) { (function() { var expandIPv6, ipaddr, ipv4Part, ipv4Regexes, ipv6Part, ipv6Regexes, matchCIDR, root, zoneIndex; ipaddr = {}; root = this; if ((module !== null) && module.exports) { module.exports = ipaddr; } else { root['ipaddr'] = ipaddr; } matchCIDR = function(first, second, partSize, cidrBits) { var part, shift; if (first.length !== second.length) { throw new Error("ipaddr: cannot match CIDR for objects with different lengths"); } part = 0; while (cidrBits > 0) { shift = partSize - cidrBits; if (shift < 0) { shift = 0; } if (first[part] >> shift !== second[part] >> shift) { return false; } cidrBits -= partSize; part += 1; } return true; }; ipaddr.subnetMatch = function(address, rangeList, defaultName) { var k, len, rangeName, rangeSubnets, subnet; if (defaultName == null) { defaultName = 'unicast'; } for (rangeName in rangeList) { rangeSubnets = rangeList[rangeName]; if (rangeSubnets[0] && !(rangeSubnets[0] instanceof Array)) { rangeSubnets = [rangeSubnets]; } for (k = 0, len = rangeSubnets.length; k < len; k++) { subnet = rangeSubnets[k]; if (address.kind() === subnet[0].kind()) { if (address.match.apply(address, subnet)) { return rangeName; } } } } return defaultName; }; ipaddr.IPv4 = (function() { function IPv4(octets) { var k, len, octet; if (octets.length !== 4) { throw new Error("ipaddr: ipv4 octet count should be 4"); } for (k = 0, len = octets.length; k < len; k++) { octet = octets[k]; if (!((0 <= octet && octet <= 255))) { throw new Error("ipaddr: ipv4 octet should fit in 8 bits"); } } this.octets = octets; } IPv4.prototype.kind = function() { return 'ipv4'; }; IPv4.prototype.toString = function() { return this.octets.join("."); }; IPv4.prototype.toNormalizedString = function() { return this.toString(); }; IPv4.prototype.toByteArray = function() { return this.octets.slice(0); }; IPv4.prototype.match = function(other, cidrRange) { var ref; if (cidrRange === void 0) { ref = other, other = ref[0], cidrRange = ref[1]; } if (other.kind() !== 'ipv4') { throw new Error("ipaddr: cannot match ipv4 address with non-ipv4 one"); } return matchCIDR(this.octets, other.octets, 8, cidrRange); }; IPv4.prototype.SpecialRanges = { unspecified: [[new IPv4([0, 0, 0, 0]), 8]], broadcast: [[new IPv4([255, 255, 255, 255]), 32]], multicast: [[new IPv4([224, 0, 0, 0]), 4]], linkLocal: [[new IPv4([169, 254, 0, 0]), 16]], loopback: [[new IPv4([127, 0, 0, 0]), 8]], carrierGradeNat: [[new IPv4([100, 64, 0, 0]), 10]], "private": [[new IPv4([10, 0, 0, 0]), 8], [new IPv4([172, 16, 0, 0]), 12], [new IPv4([192, 168, 0, 0]), 16]], reserved: [[new IPv4([192, 0, 0, 0]), 24], [new IPv4([192, 0, 2, 0]), 24], [new IPv4([192, 88, 99, 0]), 24], [new IPv4([198, 51, 100, 0]), 24], [new IPv4([203, 0, 113, 0]), 24], [new IPv4([240, 0, 0, 0]), 4]] }; IPv4.prototype.range = function() { return ipaddr.subnetMatch(this, this.SpecialRanges); }; IPv4.prototype.toIPv4MappedAddress = function() { return ipaddr.IPv6.parse("::ffff:" + (this.toString())); }; IPv4.prototype.prefixLengthFromSubnetMask = function() { var cidr, i, k, octet, stop, zeros, zerotable; zerotable = { 0: 8, 128: 7, 192: 6, 224: 5, 240: 4, 248: 3, 252: 2, 254: 1, 255: 0 }; cidr = 0; stop = false; for (i = k = 3; k >= 0; i = k += -1) { octet = this.octets[i]; if (octet in zerotable) { zeros = zerotable[octet]; if (stop && zeros !== 0) { return null; } if (zeros !== 8) { stop = true; } cidr += zeros; } else { return null; } } return 32 - cidr; }; return IPv4; })(); ipv4Part = "(0?\\d+|0x[a-f0-9]+)"; ipv4Regexes = { fourOctet: new RegExp("^" + ipv4Part + "\\." + ipv4Part + "\\." + ipv4Part + "\\." + ipv4Part + "$", 'i'), longValue: new RegExp("^" + ipv4Part + "$", 'i') }; ipaddr.IPv4.parser = function(string) { var match, parseIntAuto, part, shift, value; parseIntAuto = function(string) { if (string[0] === "0" && string[1] !== "x") { return parseInt(string, 8); } else { return parseInt(string); } }; if (match = string.match(ipv4Regexes.fourOctet)) { return (function() { var k, len, ref, results; ref = match.slice(1, 6); results = []; for (k = 0, len = ref.length; k < len; k++) { part = ref[k]; results.push(parseIntAuto(part)); } return results; })(); } else if (match = string.match(ipv4Regexes.longValue)) { value = parseIntAuto(match[1]); if (value > 0xffffffff || value < 0) { throw new Error("ipaddr: address outside defined range"); } return ((function() { var k, results; results = []; for (shift = k = 0; k <= 24; shift = k += 8) { results.push((value >> shift) & 0xff); } return results; })()).reverse(); } else { return null; } }; ipaddr.IPv6 = (function() { function IPv6(parts, zoneId) { var i, k, l, len, part, ref; if (parts.length === 16) { this.parts = []; for (i = k = 0; k <= 14; i = k += 2) { this.parts.push((parts[i] << 8) | parts[i + 1]); } } else if (parts.length === 8) { this.parts = parts; } else { throw new Error("ipaddr: ipv6 part count should be 8 or 16"); } ref = this.parts; for (l = 0, len = ref.length; l < len; l++) { part = ref[l]; if (!((0 <= part && part <= 0xffff))) { throw new Error("ipaddr: ipv6 part should fit in 16 bits"); } } if (zoneId) { this.zoneId = zoneId; } } IPv6.prototype.kind = function() { return 'ipv6'; }; IPv6.prototype.toString = function() { return this.toNormalizedString().replace(/((^|:)(0(:|$))+)/, '::'); }; IPv6.prototype.toRFC5952String = function() { var bestMatchIndex, bestMatchLength, match, regex, string; regex = /((^|:)(0(:|$)){2,})/g; string = this.toNormalizedString(); bestMatchIndex = 0; bestMatchLength = -1; while ((match = regex.exec(string))) { if (match[0].length > bestMatchLength) { bestMatchIndex = match.index; bestMatchLength = match[0].length; } } if (bestMatchLength < 0) { return string; } return string.substring(0, bestMatchIndex) + '::' + string.substring(bestMatchIndex + bestMatchLength); }; IPv6.prototype.toByteArray = function() { var bytes, k, len, part, ref; bytes = []; ref = this.parts; for (k = 0, len = ref.length; k < len; k++) { part = ref[k]; bytes.push(part >> 8); bytes.push(part & 0xff); } return bytes; }; IPv6.prototype.toNormalizedString = function() { var addr, part, suffix; addr = ((function() { var k, len, ref, results; ref = this.parts; results = []; for (k = 0, len = ref.length; k < len; k++) { part = ref[k]; results.push(part.toString(16)); } return results; }).call(this)).join(":"); suffix = ''; if (this.zoneId) { suffix = '%' + this.zoneId; } return addr + suffix; }; IPv6.prototype.toFixedLengthString = function() { var addr, part, suffix; addr = ((function() { var k, len, ref, results; ref = this.parts; results = []; for (k = 0, len = ref.length; k < len; k++) { part = ref[k]; results.push(part.toString(16).padStart(4, '0')); } return results; }).call(this)).join(":"); suffix = ''; if (this.zoneId) { suffix = '%' + this.zoneId; } return addr + suffix; }; IPv6.prototype.match = function(other, cidrRange) { var ref; if (cidrRange === void 0) { ref = other, other = ref[0], cidrRange = ref[1]; } if (other.kind() !== 'ipv6') { throw new Error("ipaddr: cannot match ipv6 address with non-ipv6 one"); } return matchCIDR(this.parts, other.parts, 16, cidrRange); }; IPv6.prototype.SpecialRanges = { unspecified: [new IPv6([0, 0, 0, 0, 0, 0, 0, 0]), 128], linkLocal: [new IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 0]), 10], multicast: [new IPv6([0xff00, 0, 0, 0, 0, 0, 0, 0]), 8], loopback: [new IPv6([0, 0, 0, 0, 0, 0, 0, 1]), 128], uniqueLocal: [new IPv6([0xfc00, 0, 0, 0, 0, 0, 0, 0]), 7], ipv4Mapped: [new IPv6([0, 0, 0, 0, 0, 0xffff, 0, 0]), 96], rfc6145: [new IPv6([0, 0, 0, 0, 0xffff, 0, 0, 0]), 96], rfc6052: [new IPv6([0x64, 0xff9b, 0, 0, 0, 0, 0, 0]), 96], '6to4': [new IPv6([0x2002, 0, 0, 0, 0, 0, 0, 0]), 16], teredo: [new IPv6([0x2001, 0, 0, 0, 0, 0, 0, 0]), 32], reserved: [[new IPv6([0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]), 32]] }; IPv6.prototype.range = function() { return ipaddr.subnetMatch(this, this.SpecialRanges); }; IPv6.prototype.isIPv4MappedAddress = function() { return this.range() === 'ipv4Mapped'; }; IPv6.prototype.toIPv4Address = function() { var high, low, ref; if (!this.isIPv4MappedAddress()) { throw new Error("ipaddr: trying to convert a generic ipv6 address to ipv4"); } ref = this.parts.slice(-2), high = ref[0], low = ref[1]; return new ipaddr.IPv4([high >> 8, high & 0xff, low >> 8, low & 0xff]); }; IPv6.prototype.prefixLengthFromSubnetMask = function() { var cidr, i, k, part, stop, zeros, zerotable; zerotable = { 0: 16, 32768: 15, 49152: 14, 57344: 13, 61440: 12, 63488: 11, 64512: 10, 65024: 9, 65280: 8, 65408: 7, 65472: 6, 65504: 5, 65520: 4, 65528: 3, 65532: 2, 65534: 1, 65535: 0 }; cidr = 0; stop = false; for (i = k = 7; k >= 0; i = k += -1) { part = this.parts[i]; if (part in zerotable) { zeros = zerotable[part]; if (stop && zeros !== 0) { return null; } if (zeros !== 16) { stop = true; } cidr += zeros; } else { return null; } } return 128 - cidr; }; return IPv6; })(); ipv6Part = "(?:[0-9a-f]+::?)+"; zoneIndex = "%[0-9a-z]{1,}"; ipv6Regexes = { zoneIndex: new RegExp(zoneIndex, 'i'), "native": new RegExp("^(::)?(" + ipv6Part + ")?([0-9a-f]+)?(::)?(" + zoneIndex + ")?$", 'i'), transitional: new RegExp(("^((?:" + ipv6Part + ")|(?:::)(?:" + ipv6Part + ")?)") + (ipv4Part + "\\." + ipv4Part + "\\." + ipv4Part + "\\." + ipv4Part) + ("(" + zoneIndex + ")?$"), 'i') }; expandIPv6 = function(string, parts) { var colonCount, lastColon, part, replacement, replacementCount, zoneId; if (string.indexOf('::') !== string.lastIndexOf('::')) { return null; } zoneId = (string.match(ipv6Regexes['zoneIndex']) || [])[0]; if (zoneId) { zoneId = zoneId.substring(1); string = string.replace(/%.+$/, ''); } colonCount = 0; lastColon = -1; while ((lastColon = string.indexOf(':', lastColon + 1)) >= 0) { colonCount++; } if (string.substr(0, 2) === '::') { colonCount--; } if (string.substr(-2, 2) === '::') { colonCount--; } if (colonCount > parts) { return null; } replacementCount = parts - colonCount; replacement = ':'; while (replacementCount--) { replacement += '0:'; } string = string.replace('::', replacement); if (string[0] === ':') { string = string.slice(1); } if (string[string.length - 1] === ':') { string = string.slice(0, -1); } parts = (function() { var k, len, ref, results; ref = string.split(":"); results = []; for (k = 0, len = ref.length; k < len; k++) { part = ref[k]; results.push(parseInt(part, 16)); } return results; })(); return { parts: parts, zoneId: zoneId }; }; ipaddr.IPv6.parser = function(string) { var addr, k, len, match, octet, octets, zoneId; if (ipv6Regexes['native'].test(string)) { return expandIPv6(string, 8); } else if (match = string.match(ipv6Regexes['transitional'])) { zoneId = match[6] || ''; addr = expandIPv6(match[1].slice(0, -1) + zoneId, 6); if (addr.parts) { octets = [parseInt(match[2]), parseInt(match[3]), parseInt(match[4]), parseInt(match[5])]; for (k = 0, len = octets.length; k < len; k++) { octet = octets[k]; if (!((0 <= octet && octet <= 255))) { return null; } } addr.parts.push(octets[0] << 8 | octets[1]); addr.parts.push(octets[2] << 8 | octets[3]); return { parts: addr.parts, zoneId: addr.zoneId }; } } return null; }; ipaddr.IPv4.isIPv4 = ipaddr.IPv6.isIPv6 = function(string) { return this.parser(string) !== null; }; ipaddr.IPv4.isValid = function(string) { try { new this(this.parser(string)); return true; } catch (error1) { return false; } }; ipaddr.IPv4.isValidFourPartDecimal = function(string) { if (ipaddr.IPv4.isValid(string) && string.match(/^(0|[1-9]\d*)(\.(0|[1-9]\d*)){3}$/)) { return true; } else { return false; } }; ipaddr.IPv6.isValid = function(string) { var addr; if (typeof string === "string" && string.indexOf(":") === -1) { return false; } try { addr = this.parser(string); new this(addr.parts, addr.zoneId); return true; } catch (error1) { return false; } }; ipaddr.IPv4.parse = function(string) { var parts; parts = this.parser(string); if (parts === null) { throw new Error("ipaddr: string is not formatted like ip address"); } return new this(parts); }; ipaddr.IPv6.parse = function(string) { var addr; addr = this.parser(string); if (addr.parts === null) { throw new Error("ipaddr: string is not formatted like ip address"); } return new this(addr.parts, addr.zoneId); }; ipaddr.IPv4.parseCIDR = function(string) { var maskLength, match, parsed; if (match = string.match(/^(.+)\/(\d+)$/)) { maskLength = parseInt(match[2]); if (maskLength >= 0 && maskLength <= 32) { parsed = [this.parse(match[1]), maskLength]; Object.defineProperty(parsed, 'toString', { value: function() { return this.join('/'); } }); return parsed; } } throw new Error("ipaddr: string is not formatted like an IPv4 CIDR range"); }; ipaddr.IPv4.subnetMaskFromPrefixLength = function(prefix) { var filledOctetCount, j, octets; prefix = parseInt(prefix); if (prefix < 0 || prefix > 32) { throw new Error('ipaddr: invalid IPv4 prefix length'); } octets = [0, 0, 0, 0]; j = 0; filledOctetCount = Math.floor(prefix / 8); while (j < filledOctetCount) { octets[j] = 255; j++; } if (filledOctetCount < 4) { octets[filledOctetCount] = Math.pow(2, prefix % 8) - 1 << 8 - (prefix % 8); } return new this(octets); }; ipaddr.IPv4.broadcastAddressFromCIDR = function(string) { var cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; try { cidr = this.parseCIDR(string); ipInterfaceOctets = cidr[0].toByteArray(); subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); octets = []; i = 0; while (i < 4) { octets.push(parseInt(ipInterfaceOctets[i], 10) | parseInt(subnetMaskOctets[i], 10) ^ 255); i++; } return new this(octets); } catch (error1) { throw new Error('ipaddr: the address does not have IPv4 CIDR format'); } }; ipaddr.IPv4.networkAddressFromCIDR = function(string) { var cidr, i, ipInterfaceOctets, octets, subnetMaskOctets; try { cidr = this.parseCIDR(string); ipInterfaceOctets = cidr[0].toByteArray(); subnetMaskOctets = this.subnetMaskFromPrefixLength(cidr[1]).toByteArray(); octets = []; i = 0; while (i < 4) { octets.push(parseInt(ipInterfaceOctets[i], 10) & parseInt(subnetMaskOctets[i], 10)); i++; } return new this(octets); } catch (error1) { throw new Error('ipaddr: the address does not have IPv4 CIDR format'); } }; ipaddr.IPv6.parseCIDR = function(string) { var maskLength, match, parsed; if (match = string.match(/^(.+)\/(\d+)$/)) { maskLength = parseInt(match[2]); if (maskLength >= 0 && maskLength <= 128) { parsed = [this.parse(match[1]), maskLength]; Object.defineProperty(parsed, 'toString', { value: function() { return this.join('/'); } }); return parsed; } } throw new Error("ipaddr: string is not formatted like an IPv6 CIDR range"); }; ipaddr.isValid = function(string) { return ipaddr.IPv6.isValid(string) || ipaddr.IPv4.isValid(string); }; ipaddr.parse = function(string) { if (ipaddr.IPv6.isValid(string)) { return ipaddr.IPv6.parse(string); } else if (ipaddr.IPv4.isValid(string)) { return ipaddr.IPv4.parse(string); } else { throw new Error("ipaddr: the address has neither IPv6 nor IPv4 format"); } }; ipaddr.parseCIDR = function(string) { try { return ipaddr.IPv6.parseCIDR(string); } catch (error1) { try { return ipaddr.IPv4.parseCIDR(string); } catch (error1) { throw new Error("ipaddr: the address has neither IPv6 nor IPv4 CIDR format"); } } }; ipaddr.fromByteArray = function(bytes) { var length; length = bytes.length; if (length === 4) { return new ipaddr.IPv4(bytes); } else if (length === 16) { return new ipaddr.IPv6(bytes); } else { throw new Error("ipaddr: the binary input is neither an IPv6 nor IPv4 address"); } }; ipaddr.process = function(string) { var addr; addr = this.parse(string); if (addr.kind() === 'ipv6' && addr.isIPv4MappedAddress()) { return addr.toIPv4Address(); } else { return addr; } }; }).call(commonjsGlobal); }(ipaddr$1)); /*! * proxy-addr * Copyright(c) 2014-2016 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ proxyAddr.exports = proxyaddr$1; proxyAddr.exports.all = alladdrs; proxyAddr.exports.compile = compile$2; /** * Module dependencies. * @private */ var forwarded = forwarded_1; var ipaddr = ipaddr$1.exports; /** * Variables. * @private */ var DIGIT_REGEXP = /^[0-9]+$/; var isip = ipaddr.isValid; var parseip = ipaddr.parse; /** * Pre-defined IP ranges. * @private */ var IP_RANGES = { linklocal: ['169.254.0.0/16', 'fe80::/10'], loopback: ['127.0.0.1/8', '::1/128'], uniquelocal: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7'] }; /** * Get all addresses in the request, optionally stopping * at the first untrusted. * * @param {Object} request * @param {Function|Array|String} [trust] * @public */ function alladdrs (req, trust) { // get addresses var addrs = forwarded(req); if (!trust) { // Return all addresses return addrs } if (typeof trust !== 'function') { trust = compile$2(trust); } for (var i = 0; i < addrs.length - 1; i++) { if (trust(addrs[i], i)) continue addrs.length = i + 1; } return addrs } /** * Compile argument into trust function. * * @param {Array|String} val * @private */ function compile$2 (val) { if (!val) { throw new TypeError('argument is required') } var trust; if (typeof val === 'string') { trust = [val]; } else if (Array.isArray(val)) { trust = val.slice(); } else { throw new TypeError('unsupported trust argument') } for (var i = 0; i < trust.length; i++) { val = trust[i]; if (!Object.prototype.hasOwnProperty.call(IP_RANGES, val)) { continue } // Splice in pre-defined range val = IP_RANGES[val]; trust.splice.apply(trust, [i, 1].concat(val)); i += val.length - 1; } return compileTrust(compileRangeSubnets(trust)) } /** * Compile `arr` elements into range subnets. * * @param {Array} arr * @private */ function compileRangeSubnets (arr) { var rangeSubnets = new Array(arr.length); for (var i = 0; i < arr.length; i++) { rangeSubnets[i] = parseipNotation(arr[i]); } return rangeSubnets } /** * Compile range subnet array into trust function. * * @param {Array} rangeSubnets * @private */ function compileTrust (rangeSubnets) { // Return optimized function based on length var len = rangeSubnets.length; return len === 0 ? trustNone : len === 1 ? trustSingle(rangeSubnets[0]) : trustMulti(rangeSubnets) } /** * Parse IP notation string into range subnet. * * @param {String} note * @private */ function parseipNotation (note) { var pos = note.lastIndexOf('/'); var str = pos !== -1 ? note.substring(0, pos) : note; if (!isip(str)) { throw new TypeError('invalid IP address: ' + str) } var ip = parseip(str); if (pos === -1 && ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) { // Store as IPv4 ip = ip.toIPv4Address(); } var max = ip.kind() === 'ipv6' ? 128 : 32; var range = pos !== -1 ? note.substring(pos + 1, note.length) : null; if (range === null) { range = max; } else if (DIGIT_REGEXP.test(range)) { range = parseInt(range, 10); } else if (ip.kind() === 'ipv4' && isip(range)) { range = parseNetmask(range); } else { range = null; } if (range <= 0 || range > max) { throw new TypeError('invalid range on address: ' + note) } return [ip, range] } /** * Parse netmask string into CIDR range. * * @param {String} netmask * @private */ function parseNetmask (netmask) { var ip = parseip(netmask); var kind = ip.kind(); return kind === 'ipv4' ? ip.prefixLengthFromSubnetMask() : null } /** * Determine address of proxied request. * * @param {Object} request * @param {Function|Array|String} trust * @public */ function proxyaddr$1 (req, trust) { if (!req) { throw new TypeError('req argument is required') } if (!trust) { throw new TypeError('trust argument is required') } var addrs = alladdrs(req, trust); var addr = addrs[addrs.length - 1]; return addr } /** * Static trust function to trust nothing. * * @private */ function trustNone () { return false } /** * Compile trust function for multiple subnets. * * @param {Array} subnets * @private */ function trustMulti (subnets) { return function trust (addr) { if (!isip(addr)) return false var ip = parseip(addr); var ipconv; var kind = ip.kind(); for (var i = 0; i < subnets.length; i++) { var subnet = subnets[i]; var subnetip = subnet[0]; var subnetkind = subnetip.kind(); var subnetrange = subnet[1]; var trusted = ip; if (kind !== subnetkind) { if (subnetkind === 'ipv4' && !ip.isIPv4MappedAddress()) { // Incompatible IP addresses continue } if (!ipconv) { // Convert IP to match subnet IP kind ipconv = subnetkind === 'ipv4' ? ip.toIPv4Address() : ip.toIPv4MappedAddress(); } trusted = ipconv; } if (trusted.match(subnetip, subnetrange)) { return true } } return false } } /** * Compile trust function for single subnet. * * @param {Object} subnet * @private */ function trustSingle (subnet) { var subnetip = subnet[0]; var subnetkind = subnetip.kind(); var subnetisipv4 = subnetkind === 'ipv4'; var subnetrange = subnet[1]; return function trust (addr) { if (!isip(addr)) return false var ip = parseip(addr); var kind = ip.kind(); if (kind !== subnetkind) { if (subnetisipv4 && !ip.isIPv4MappedAddress()) { // Incompatible IP addresses return false } // Convert IP to match subnet IP kind ip = subnetisipv4 ? ip.toIPv4Address() : ip.toIPv4MappedAddress(); } return ip.match(subnetip, subnetrange) } } /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ (function (exports) { /** * Module dependencies. * @api private */ var Buffer = safeBuffer.exports.Buffer; var contentDisposition = contentDisposition$2.exports; var contentType = contentType$3; var deprecate = depd_1('express'); var flatten = arrayFlatten_1; var mime = send$3.exports.mime; var etag = etag_1; var proxyaddr = proxyAddr.exports; var qs = lib; var querystring = require$$8__default$1['default']; /** * Return strong ETag for `body`. * * @param {String|Buffer} body * @param {String} [encoding] * @return {String} * @api private */ exports.etag = createETagGenerator({ weak: false }); /** * Return weak ETag for `body`. * * @param {String|Buffer} body * @param {String} [encoding] * @return {String} * @api private */ exports.wetag = createETagGenerator({ weak: true }); /** * Check if `path` looks absolute. * * @param {String} path * @return {Boolean} * @api private */ exports.isAbsolute = function(path){ if ('/' === path[0]) return true; if (':' === path[1] && ('\\' === path[2] || '/' === path[2])) return true; // Windows device path if ('\\\\' === path.substring(0, 2)) return true; // Microsoft Azure absolute path }; /** * Flatten the given `arr`. * * @param {Array} arr * @return {Array} * @api private */ exports.flatten = deprecate.function(flatten, 'utils.flatten: use array-flatten npm module instead'); /** * Normalize the given `type`, for example "html" becomes "text/html". * * @param {String} type * @return {Object} * @api private */ exports.normalizeType = function(type){ return ~type.indexOf('/') ? acceptParams(type) : { value: mime.lookup(type), params: {} }; }; /** * Normalize `types`, for example "html" becomes "text/html". * * @param {Array} types * @return {Array} * @api private */ exports.normalizeTypes = function(types){ var ret = []; for (var i = 0; i < types.length; ++i) { ret.push(exports.normalizeType(types[i])); } return ret; }; /** * Generate Content-Disposition header appropriate for the filename. * non-ascii filenames are urlencoded and a filename* parameter is added * * @param {String} filename * @return {String} * @api private */ exports.contentDisposition = deprecate.function(contentDisposition, 'utils.contentDisposition: use content-disposition npm module instead'); /** * Parse accept params `str` returning an * object with `.value`, `.quality` and `.params`. * also includes `.originalIndex` for stable sorting * * @param {String} str * @return {Object} * @api private */ function acceptParams(str, index) { var parts = str.split(/ *; */); var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index }; for (var i = 1; i < parts.length; ++i) { var pms = parts[i].split(/ *= */); if ('q' === pms[0]) { ret.quality = parseFloat(pms[1]); } else { ret.params[pms[0]] = pms[1]; } } return ret; } /** * Compile "etag" value to function. * * @param {Boolean|String|Function} val * @return {Function} * @api private */ exports.compileETag = function(val) { var fn; if (typeof val === 'function') { return val; } switch (val) { case true: fn = exports.wetag; break; case false: break; case 'strong': fn = exports.etag; break; case 'weak': fn = exports.wetag; break; default: throw new TypeError('unknown value for etag function: ' + val); } return fn; }; /** * Compile "query parser" value to function. * * @param {String|Function} val * @return {Function} * @api private */ exports.compileQueryParser = function compileQueryParser(val) { var fn; if (typeof val === 'function') { return val; } switch (val) { case true: fn = querystring.parse; break; case false: fn = newObject; break; case 'extended': fn = parseExtendedQueryString; break; case 'simple': fn = querystring.parse; break; default: throw new TypeError('unknown value for query parser function: ' + val); } return fn; }; /** * Compile "proxy trust" value to function. * * @param {Boolean|String|Number|Array|Function} val * @return {Function} * @api private */ exports.compileTrust = function(val) { if (typeof val === 'function') return val; if (val === true) { // Support plain true/false return function(){ return true }; } if (typeof val === 'number') { // Support trusting hop count return function(a, i){ return i < val }; } if (typeof val === 'string') { // Support comma-separated values val = val.split(/ *, */); } return proxyaddr.compile(val || []); }; /** * Set the charset in a given Content-Type string. * * @param {String} type * @param {String} charset * @return {String} * @api private */ exports.setCharset = function setCharset(type, charset) { if (!type || !charset) { return type; } // parse type var parsed = contentType.parse(type); // set charset parsed.parameters.charset = charset; // format type return contentType.format(parsed); }; /** * Create an ETag generator function, generating ETags with * the given options. * * @param {object} options * @return {function} * @private */ function createETagGenerator (options) { return function generateETag (body, encoding) { var buf = !Buffer.isBuffer(body) ? Buffer.from(body, encoding) : body; return etag(buf, options) } } /** * Parse an extended query string with qs. * * @return {Object} * @private */ function parseExtendedQueryString(str) { return qs.parse(str, { allowPrototypes: true }); } /** * Return new empty object. * * @return {Object} * @api private */ function newObject() { return {}; } }(utils$9)); /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ (function (module, exports) { /** * Module dependencies. * @private */ var finalhandler = finalhandler_1; var Router = router$1.exports; var methods = methods$2; var middleware = init$1; var query$1 = query; var debug = src$1.exports('express:application'); var View = view; var http = require$$0__default$3['default']; var compileETag = utils$9.compileETag; var compileQueryParser = utils$9.compileQueryParser; var compileTrust = utils$9.compileTrust; var deprecate = depd_1('express'); var flatten = arrayFlatten_1; var merge = utilsMerge.exports; var resolve = require$$0__default$5['default'].resolve; var setPrototypeOf = setprototypeof; var slice = Array.prototype.slice; /** * Application prototype. */ var app = module.exports = {}; /** * Variable for trust proxy inheritance back-compat * @private */ var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default'; /** * Initialize the server. * * - setup default configuration * - setup default middleware * - setup route reflection methods * * @private */ app.init = function init() { this.cache = {}; this.engines = {}; this.settings = {}; this.defaultConfiguration(); }; /** * Initialize application configuration. * @private */ app.defaultConfiguration = function defaultConfiguration() { var env = process.env.NODE_ENV || 'development'; // default settings this.enable('x-powered-by'); this.set('etag', 'weak'); this.set('env', env); this.set('query parser', 'extended'); this.set('subdomain offset', 2); this.set('trust proxy', false); // trust proxy inherit back-compat Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: true }); debug('booting in %s mode', env); this.on('mount', function onmount(parent) { // inherit trust proxy if (this.settings[trustProxyDefaultSymbol] === true && typeof parent.settings['trust proxy fn'] === 'function') { delete this.settings['trust proxy']; delete this.settings['trust proxy fn']; } // inherit protos setPrototypeOf(this.request, parent.request); setPrototypeOf(this.response, parent.response); setPrototypeOf(this.engines, parent.engines); setPrototypeOf(this.settings, parent.settings); }); // setup locals this.locals = Object.create(null); // top-most app is mounted at / this.mountpath = '/'; // default locals this.locals.settings = this.settings; // default configuration this.set('view', View); this.set('views', resolve('views')); this.set('jsonp callback name', 'callback'); if (env === 'production') { this.enable('view cache'); } Object.defineProperty(this, 'router', { get: function() { throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.'); } }); }; /** * lazily adds the base router if it has not yet been added. * * We cannot add the base router in the defaultConfiguration because * it reads app settings which might be set after that has run. * * @private */ app.lazyrouter = function lazyrouter() { if (!this._router) { this._router = new Router({ caseSensitive: this.enabled('case sensitive routing'), strict: this.enabled('strict routing') }); this._router.use(query$1(this.get('query parser fn'))); this._router.use(middleware.init(this)); } }; /** * Dispatch a req, res pair into the application. Starts pipeline processing. * * If no callback is provided, then default error handlers will respond * in the event of an error bubbling through the stack. * * @private */ app.handle = function handle(req, res, callback) { var router = this._router; // final handler var done = callback || finalhandler(req, res, { env: this.get('env'), onerror: logerror.bind(this) }); // no routes if (!router) { debug('no routes defined on app'); done(); return; } router.handle(req, res, done); }; /** * Proxy `Router#use()` to add middleware to the app router. * See Router#use() documentation for details. * * If the _fn_ parameter is an express app, then it will be * mounted at the _route_ specified. * * @public */ app.use = function use(fn) { var offset = 0; var path = '/'; // default path to '/' // disambiguate app.use([fn]) if (typeof fn !== 'function') { var arg = fn; while (Array.isArray(arg) && arg.length !== 0) { arg = arg[0]; } // first arg is the path if (typeof arg !== 'function') { offset = 1; path = fn; } } var fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) { throw new TypeError('app.use() requires a middleware function') } // setup router this.lazyrouter(); var router = this._router; fns.forEach(function (fn) { // non-express app if (!fn || !fn.handle || !fn.set) { return router.use(path, fn); } debug('.use app under %s', path); fn.mountpath = path; fn.parent = this; // restore .app property on req and res router.use(path, function mounted_app(req, res, next) { var orig = req.app; fn.handle(req, res, function (err) { setPrototypeOf(req, orig.request); setPrototypeOf(res, orig.response); next(err); }); }); // mounted an app fn.emit('mount', this); }, this); return this; }; /** * Proxy to the app `Router#route()` * Returns a new `Route` instance for the _path_. * * Routes are isolated middleware stacks for specific paths. * See the Route api docs for details. * * @public */ app.route = function route(path) { this.lazyrouter(); return this._router.route(path); }; /** * Register the given template engine callback `fn` * as `ext`. * * By default will `require()` the engine based on the * file extension. For example if you try to render * a "foo.ejs" file Express will invoke the following internally: * * app.engine('ejs', require('ejs').__express); * * For engines that do not provide `.__express` out of the box, * or if you wish to "map" a different extension to the template engine * you may use this method. For example mapping the EJS template engine to * ".html" files: * * app.engine('html', require('ejs').renderFile); * * In this case EJS provides a `.renderFile()` method with * the same signature that Express expects: `(path, options, callback)`, * though note that it aliases this method as `ejs.__express` internally * so if you're using ".ejs" extensions you dont need to do anything. * * Some template engines do not follow this convention, the * [Consolidate.js](https://github.com/tj/consolidate.js) * library was created to map all of node's popular template * engines to follow this convention, thus allowing them to * work seamlessly within Express. * * @param {String} ext * @param {Function} fn * @return {app} for chaining * @public */ app.engine = function engine(ext, fn) { if (typeof fn !== 'function') { throw new Error('callback function required'); } // get file extension var extension = ext[0] !== '.' ? '.' + ext : ext; // store engine this.engines[extension] = fn; return this; }; /** * Proxy to `Router#param()` with one added api feature. The _name_ parameter * can be an array of names. * * See the Router#param() docs for more details. * * @param {String|Array} name * @param {Function} fn * @return {app} for chaining * @public */ app.param = function param(name, fn) { this.lazyrouter(); if (Array.isArray(name)) { for (var i = 0; i < name.length; i++) { this.param(name[i], fn); } return this; } this._router.param(name, fn); return this; }; /** * Assign `setting` to `val`, or return `setting`'s value. * * app.set('foo', 'bar'); * app.set('foo'); * // => "bar" * * Mounted servers inherit their parent server's settings. * * @param {String} setting * @param {*} [val] * @return {Server} for chaining * @public */ app.set = function set(setting, val) { if (arguments.length === 1) { // app.get(setting) return this.settings[setting]; } debug('set "%s" to %o', setting, val); // set value this.settings[setting] = val; // trigger matched settings switch (setting) { case 'etag': this.set('etag fn', compileETag(val)); break; case 'query parser': this.set('query parser fn', compileQueryParser(val)); break; case 'trust proxy': this.set('trust proxy fn', compileTrust(val)); // trust proxy inherit back-compat Object.defineProperty(this.settings, trustProxyDefaultSymbol, { configurable: true, value: false }); break; } return this; }; /** * Return the app's absolute pathname * based on the parent(s) that have * mounted it. * * For example if the application was * mounted as "/admin", which itself * was mounted as "/blog" then the * return value would be "/blog/admin". * * @return {String} * @private */ app.path = function path() { return this.parent ? this.parent.path() + this.mountpath : ''; }; /** * Check if `setting` is enabled (truthy). * * app.enabled('foo') * // => false * * app.enable('foo') * app.enabled('foo') * // => true * * @param {String} setting * @return {Boolean} * @public */ app.enabled = function enabled(setting) { return Boolean(this.set(setting)); }; /** * Check if `setting` is disabled. * * app.disabled('foo') * // => true * * app.enable('foo') * app.disabled('foo') * // => false * * @param {String} setting * @return {Boolean} * @public */ app.disabled = function disabled(setting) { return !this.set(setting); }; /** * Enable `setting`. * * @param {String} setting * @return {app} for chaining * @public */ app.enable = function enable(setting) { return this.set(setting, true); }; /** * Disable `setting`. * * @param {String} setting * @return {app} for chaining * @public */ app.disable = function disable(setting) { return this.set(setting, false); }; /** * Delegate `.VERB(...)` calls to `router.VERB(...)`. */ methods.forEach(function(method){ app[method] = function(path){ if (method === 'get' && arguments.length === 1) { // app.get(setting) return this.set(path); } this.lazyrouter(); var route = this._router.route(path); route[method].apply(route, slice.call(arguments, 1)); return this; }; }); /** * Special-cased "all" method, applying the given route `path`, * middleware, and callback to _every_ HTTP method. * * @param {String} path * @param {Function} ... * @return {app} for chaining * @public */ app.all = function all(path) { this.lazyrouter(); var route = this._router.route(path); var args = slice.call(arguments, 1); for (var i = 0; i < methods.length; i++) { route[methods[i]].apply(route, args); } return this; }; // del -> delete alias app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead'); /** * Render the given view `name` name with `options` * and a callback accepting an error and the * rendered template string. * * Example: * * app.render('email', { name: 'Tobi' }, function(err, html){ * // ... * }) * * @param {String} name * @param {Object|Function} options or fn * @param {Function} callback * @public */ app.render = function render(name, options, callback) { var cache = this.cache; var done = callback; var engines = this.engines; var opts = options; var renderOptions = {}; var view; // support callback function as second arg if (typeof options === 'function') { done = options; opts = {}; } // merge app.locals merge(renderOptions, this.locals); // merge options._locals if (opts._locals) { merge(renderOptions, opts._locals); } // merge options merge(renderOptions, opts); // set .cache unless explicitly provided if (renderOptions.cache == null) { renderOptions.cache = this.enabled('view cache'); } // primed cache if (renderOptions.cache) { view = cache[name]; } // view if (!view) { var View = this.get('view'); view = new View(name, { defaultEngine: this.get('view engine'), root: this.get('views'), engines: engines }); if (!view.path) { var dirs = Array.isArray(view.root) && view.root.length > 1 ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"' : 'directory "' + view.root + '"'; var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs); err.view = view; return done(err); } // prime the cache if (renderOptions.cache) { cache[name] = view; } } // render tryRender(view, renderOptions, done); }; /** * Listen for connections. * * A node `http.Server` is returned, with this * application (which is a `Function`) as its * callback. If you wish to create both an HTTP * and HTTPS server you may do so with the "http" * and "https" modules as shown here: * * var http = require('http') * , https = require('https') * , express = require('express') * , app = express(); * * http.createServer(app).listen(80); * https.createServer({ ... }, app).listen(443); * * @return {http.Server} * @public */ app.listen = function listen() { var server = http.createServer(this); return server.listen.apply(server, arguments); }; /** * Log error using console.error. * * @param {Error} err * @private */ function logerror(err) { /* istanbul ignore next */ if (this.get('env') !== 'test') console.error(err.stack || err.toString()); } /** * Try rendering a view. * @private */ function tryRender(view, options, callback) { try { view.render(options, callback); } catch (err) { callback(err); } } }(application)); var negotiator = {exports: {}}; var charset = {exports: {}}; /** * negotiator * Copyright(c) 2012 Isaac Z. Schlueter * Copyright(c) 2014 Federico Romero * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ charset.exports = preferredCharsets; charset.exports.preferredCharsets = preferredCharsets; /** * Module variables. * @private */ var simpleCharsetRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/; /** * Parse the Accept-Charset header. * @private */ function parseAcceptCharset(accept) { var accepts = accept.split(','); for (var i = 0, j = 0; i < accepts.length; i++) { var charset = parseCharset(accepts[i].trim(), i); if (charset) { accepts[j++] = charset; } } // trim accepts accepts.length = j; return accepts; } /** * Parse a charset from the Accept-Charset header. * @private */ function parseCharset(str, i) { var match = simpleCharsetRegExp.exec(str); if (!match) return null; var charset = match[1]; var q = 1; if (match[2]) { var params = match[2].split(';'); for (var j = 0; j < params.length; j++) { var p = params[j].trim().split('='); if (p[0] === 'q') { q = parseFloat(p[1]); break; } } } return { charset: charset, q: q, i: i }; } /** * Get the priority of a charset. * @private */ function getCharsetPriority(charset, accepted, index) { var priority = {o: -1, q: 0, s: 0}; for (var i = 0; i < accepted.length; i++) { var spec = specify$3(charset, accepted[i], index); if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) { priority = spec; } } return priority; } /** * Get the specificity of the charset. * @private */ function specify$3(charset, spec, index) { var s = 0; if(spec.charset.toLowerCase() === charset.toLowerCase()){ s |= 1; } else if (spec.charset !== '*' ) { return null } return { i: index, o: spec.i, q: spec.q, s: s } } /** * Get the preferred charsets from an Accept-Charset header. * @public */ function preferredCharsets(accept, provided) { // RFC 2616 sec 14.2: no header = * var accepts = parseAcceptCharset(accept === undefined ? '*' : accept || ''); if (!provided) { // sorted list of all charsets return accepts .filter(isQuality$3) .sort(compareSpecs$3) .map(getFullCharset); } var priorities = provided.map(function getPriority(type, index) { return getCharsetPriority(type, accepts, index); }); // sorted list of accepted charsets return priorities.filter(isQuality$3).sort(compareSpecs$3).map(function getCharset(priority) { return provided[priorities.indexOf(priority)]; }); } /** * Compare two specs. * @private */ function compareSpecs$3(a, b) { return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; } /** * Get full charset string. * @private */ function getFullCharset(spec) { return spec.charset; } /** * Check if a spec has any quality. * @private */ function isQuality$3(spec) { return spec.q > 0; } var encoding = {exports: {}}; /** * negotiator * Copyright(c) 2012 Isaac Z. Schlueter * Copyright(c) 2014 Federico Romero * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ encoding.exports = preferredEncodings; encoding.exports.preferredEncodings = preferredEncodings; /** * Module variables. * @private */ var simpleEncodingRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/; /** * Parse the Accept-Encoding header. * @private */ function parseAcceptEncoding(accept) { var accepts = accept.split(','); var hasIdentity = false; var minQuality = 1; for (var i = 0, j = 0; i < accepts.length; i++) { var encoding = parseEncoding(accepts[i].trim(), i); if (encoding) { accepts[j++] = encoding; hasIdentity = hasIdentity || specify$2('identity', encoding); minQuality = Math.min(minQuality, encoding.q || 1); } } if (!hasIdentity) { /* * If identity doesn't explicitly appear in the accept-encoding header, * it's added to the list of acceptable encoding with the lowest q */ accepts[j++] = { encoding: 'identity', q: minQuality, i: i }; } // trim accepts accepts.length = j; return accepts; } /** * Parse an encoding from the Accept-Encoding header. * @private */ function parseEncoding(str, i) { var match = simpleEncodingRegExp.exec(str); if (!match) return null; var encoding = match[1]; var q = 1; if (match[2]) { var params = match[2].split(';'); for (var j = 0; j < params.length; j++) { var p = params[j].trim().split('='); if (p[0] === 'q') { q = parseFloat(p[1]); break; } } } return { encoding: encoding, q: q, i: i }; } /** * Get the priority of an encoding. * @private */ function getEncodingPriority(encoding, accepted, index) { var priority = {o: -1, q: 0, s: 0}; for (var i = 0; i < accepted.length; i++) { var spec = specify$2(encoding, accepted[i], index); if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) { priority = spec; } } return priority; } /** * Get the specificity of the encoding. * @private */ function specify$2(encoding, spec, index) { var s = 0; if(spec.encoding.toLowerCase() === encoding.toLowerCase()){ s |= 1; } else if (spec.encoding !== '*' ) { return null } return { i: index, o: spec.i, q: spec.q, s: s } } /** * Get the preferred encodings from an Accept-Encoding header. * @public */ function preferredEncodings(accept, provided) { var accepts = parseAcceptEncoding(accept || ''); if (!provided) { // sorted list of all encodings return accepts .filter(isQuality$2) .sort(compareSpecs$2) .map(getFullEncoding); } var priorities = provided.map(function getPriority(type, index) { return getEncodingPriority(type, accepts, index); }); // sorted list of accepted encodings return priorities.filter(isQuality$2).sort(compareSpecs$2).map(function getEncoding(priority) { return provided[priorities.indexOf(priority)]; }); } /** * Compare two specs. * @private */ function compareSpecs$2(a, b) { return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; } /** * Get full encoding string. * @private */ function getFullEncoding(spec) { return spec.encoding; } /** * Check if a spec has any quality. * @private */ function isQuality$2(spec) { return spec.q > 0; } var language = {exports: {}}; /** * negotiator * Copyright(c) 2012 Isaac Z. Schlueter * Copyright(c) 2014 Federico Romero * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ language.exports = preferredLanguages; language.exports.preferredLanguages = preferredLanguages; /** * Module variables. * @private */ var simpleLanguageRegExp = /^\s*([^\s\-;]+)(?:-([^\s;]+))?\s*(?:;(.*))?$/; /** * Parse the Accept-Language header. * @private */ function parseAcceptLanguage(accept) { var accepts = accept.split(','); for (var i = 0, j = 0; i < accepts.length; i++) { var language = parseLanguage(accepts[i].trim(), i); if (language) { accepts[j++] = language; } } // trim accepts accepts.length = j; return accepts; } /** * Parse a language from the Accept-Language header. * @private */ function parseLanguage(str, i) { var match = simpleLanguageRegExp.exec(str); if (!match) return null; var prefix = match[1], suffix = match[2], full = prefix; if (suffix) full += "-" + suffix; var q = 1; if (match[3]) { var params = match[3].split(';'); for (var j = 0; j < params.length; j++) { var p = params[j].split('='); if (p[0] === 'q') q = parseFloat(p[1]); } } return { prefix: prefix, suffix: suffix, q: q, i: i, full: full }; } /** * Get the priority of a language. * @private */ function getLanguagePriority(language, accepted, index) { var priority = {o: -1, q: 0, s: 0}; for (var i = 0; i < accepted.length; i++) { var spec = specify$1(language, accepted[i], index); if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) { priority = spec; } } return priority; } /** * Get the specificity of the language. * @private */ function specify$1(language, spec, index) { var p = parseLanguage(language); if (!p) return null; var s = 0; if(spec.full.toLowerCase() === p.full.toLowerCase()){ s |= 4; } else if (spec.prefix.toLowerCase() === p.full.toLowerCase()) { s |= 2; } else if (spec.full.toLowerCase() === p.prefix.toLowerCase()) { s |= 1; } else if (spec.full !== '*' ) { return null } return { i: index, o: spec.i, q: spec.q, s: s } } /** * Get the preferred languages from an Accept-Language header. * @public */ function preferredLanguages(accept, provided) { // RFC 2616 sec 14.4: no header = * var accepts = parseAcceptLanguage(accept === undefined ? '*' : accept || ''); if (!provided) { // sorted list of all languages return accepts .filter(isQuality$1) .sort(compareSpecs$1) .map(getFullLanguage); } var priorities = provided.map(function getPriority(type, index) { return getLanguagePriority(type, accepts, index); }); // sorted list of accepted languages return priorities.filter(isQuality$1).sort(compareSpecs$1).map(function getLanguage(priority) { return provided[priorities.indexOf(priority)]; }); } /** * Compare two specs. * @private */ function compareSpecs$1(a, b) { return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; } /** * Get full language string. * @private */ function getFullLanguage(spec) { return spec.full; } /** * Check if a spec has any quality. * @private */ function isQuality$1(spec) { return spec.q > 0; } var mediaType = {exports: {}}; /** * negotiator * Copyright(c) 2012 Isaac Z. Schlueter * Copyright(c) 2014 Federico Romero * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ mediaType.exports = preferredMediaTypes; mediaType.exports.preferredMediaTypes = preferredMediaTypes; /** * Module variables. * @private */ var simpleMediaTypeRegExp = /^\s*([^\s\/;]+)\/([^;\s]+)\s*(?:;(.*))?$/; /** * Parse the Accept header. * @private */ function parseAccept(accept) { var accepts = splitMediaTypes(accept); for (var i = 0, j = 0; i < accepts.length; i++) { var mediaType = parseMediaType(accepts[i].trim(), i); if (mediaType) { accepts[j++] = mediaType; } } // trim accepts accepts.length = j; return accepts; } /** * Parse a media type from the Accept header. * @private */ function parseMediaType(str, i) { var match = simpleMediaTypeRegExp.exec(str); if (!match) return null; var params = Object.create(null); var q = 1; var subtype = match[2]; var type = match[1]; if (match[3]) { var kvps = splitParameters(match[3]).map(splitKeyValuePair); for (var j = 0; j < kvps.length; j++) { var pair = kvps[j]; var key = pair[0].toLowerCase(); var val = pair[1]; // get the value, unwrapping quotes var value = val && val[0] === '"' && val[val.length - 1] === '"' ? val.substr(1, val.length - 2) : val; if (key === 'q') { q = parseFloat(value); break; } // store parameter params[key] = value; } } return { type: type, subtype: subtype, params: params, q: q, i: i }; } /** * Get the priority of a media type. * @private */ function getMediaTypePriority(type, accepted, index) { var priority = {o: -1, q: 0, s: 0}; for (var i = 0; i < accepted.length; i++) { var spec = specify(type, accepted[i], index); if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) { priority = spec; } } return priority; } /** * Get the specificity of the media type. * @private */ function specify(type, spec, index) { var p = parseMediaType(type); var s = 0; if (!p) { return null; } if(spec.type.toLowerCase() == p.type.toLowerCase()) { s |= 4; } else if(spec.type != '*') { return null; } if(spec.subtype.toLowerCase() == p.subtype.toLowerCase()) { s |= 2; } else if(spec.subtype != '*') { return null; } var keys = Object.keys(spec.params); if (keys.length > 0) { if (keys.every(function (k) { return spec.params[k] == '*' || (spec.params[k] || '').toLowerCase() == (p.params[k] || '').toLowerCase(); })) { s |= 1; } else { return null } } return { i: index, o: spec.i, q: spec.q, s: s, } } /** * Get the preferred media types from an Accept header. * @public */ function preferredMediaTypes(accept, provided) { // RFC 2616 sec 14.2: no header = */* var accepts = parseAccept(accept === undefined ? '*/*' : accept || ''); if (!provided) { // sorted list of all types return accepts .filter(isQuality) .sort(compareSpecs) .map(getFullType); } var priorities = provided.map(function getPriority(type, index) { return getMediaTypePriority(type, accepts, index); }); // sorted list of accepted types return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) { return provided[priorities.indexOf(priority)]; }); } /** * Compare two specs. * @private */ function compareSpecs(a, b) { return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0; } /** * Get full type string. * @private */ function getFullType(spec) { return spec.type + '/' + spec.subtype; } /** * Check if a spec has any quality. * @private */ function isQuality(spec) { return spec.q > 0; } /** * Count the number of quotes in a string. * @private */ function quoteCount(string) { var count = 0; var index = 0; while ((index = string.indexOf('"', index)) !== -1) { count++; index++; } return count; } /** * Split a key value pair. * @private */ function splitKeyValuePair(str) { var index = str.indexOf('='); var key; var val; if (index === -1) { key = str; } else { key = str.substr(0, index); val = str.substr(index + 1); } return [key, val]; } /** * Split an Accept header into media types. * @private */ function splitMediaTypes(accept) { var accepts = accept.split(','); for (var i = 1, j = 0; i < accepts.length; i++) { if (quoteCount(accepts[j]) % 2 == 0) { accepts[++j] = accepts[i]; } else { accepts[j] += ',' + accepts[i]; } } // trim accepts accepts.length = j + 1; return accepts; } /** * Split a string of parameters. * @private */ function splitParameters(str) { var parameters = str.split(';'); for (var i = 1, j = 0; i < parameters.length; i++) { if (quoteCount(parameters[j]) % 2 == 0) { parameters[++j] = parameters[i]; } else { parameters[j] += ';' + parameters[i]; } } // trim parameters parameters.length = j + 1; for (var i = 0; i < parameters.length; i++) { parameters[i] = parameters[i].trim(); } return parameters; } /*! * negotiator * Copyright(c) 2012 Federico Romero * Copyright(c) 2012-2014 Isaac Z. Schlueter * Copyright(c) 2015 Douglas Christopher Wilson * MIT Licensed */ /** * Cached loaded submodules. * @private */ var modules = Object.create(null); /** * Module exports. * @public */ negotiator.exports = Negotiator$1; negotiator.exports.Negotiator = Negotiator$1; /** * Create a Negotiator instance from a request. * @param {object} request * @public */ function Negotiator$1(request) { if (!(this instanceof Negotiator$1)) { return new Negotiator$1(request); } this.request = request; } Negotiator$1.prototype.charset = function charset(available) { var set = this.charsets(available); return set && set[0]; }; Negotiator$1.prototype.charsets = function charsets(available) { var preferredCharsets = loadModule('charset').preferredCharsets; return preferredCharsets(this.request.headers['accept-charset'], available); }; Negotiator$1.prototype.encoding = function encoding(available) { var set = this.encodings(available); return set && set[0]; }; Negotiator$1.prototype.encodings = function encodings(available) { var preferredEncodings = loadModule('encoding').preferredEncodings; return preferredEncodings(this.request.headers['accept-encoding'], available); }; Negotiator$1.prototype.language = function language(available) { var set = this.languages(available); return set && set[0]; }; Negotiator$1.prototype.languages = function languages(available) { var preferredLanguages = loadModule('language').preferredLanguages; return preferredLanguages(this.request.headers['accept-language'], available); }; Negotiator$1.prototype.mediaType = function mediaType(available) { var set = this.mediaTypes(available); return set && set[0]; }; Negotiator$1.prototype.mediaTypes = function mediaTypes(available) { var preferredMediaTypes = loadModule('mediaType').preferredMediaTypes; return preferredMediaTypes(this.request.headers.accept, available); }; // Backwards compatibility Negotiator$1.prototype.preferredCharset = Negotiator$1.prototype.charset; Negotiator$1.prototype.preferredCharsets = Negotiator$1.prototype.charsets; Negotiator$1.prototype.preferredEncoding = Negotiator$1.prototype.encoding; Negotiator$1.prototype.preferredEncodings = Negotiator$1.prototype.encodings; Negotiator$1.prototype.preferredLanguage = Negotiator$1.prototype.language; Negotiator$1.prototype.preferredLanguages = Negotiator$1.prototype.languages; Negotiator$1.prototype.preferredMediaType = Negotiator$1.prototype.mediaType; Negotiator$1.prototype.preferredMediaTypes = Negotiator$1.prototype.mediaTypes; /** * Load the given module. * @private */ function loadModule(moduleName) { var module = modules[moduleName]; if (module !== undefined) { return module; } // This uses a switch for static require analysis switch (moduleName) { case 'charset': module = charset.exports; break; case 'encoding': module = encoding.exports; break; case 'language': module = language.exports; break; case 'mediaType': module = mediaType.exports; break; default: throw new Error('Cannot find module \'' + moduleName + '\''); } // Store to prevent invoking require() modules[moduleName] = module; return module; } /*! * accepts * Copyright(c) 2014 Jonathan Ong * Copyright(c) 2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. * @private */ var Negotiator = negotiator.exports; var mime$1 = mimeTypes; /** * Module exports. * @public */ var accepts$1 = Accepts; /** * Create a new Accepts object for the given req. * * @param {object} req * @public */ function Accepts (req) { if (!(this instanceof Accepts)) { return new Accepts(req) } this.headers = req.headers; this.negotiator = new Negotiator(req); } /** * Check if the given `type(s)` is acceptable, returning * the best match when true, otherwise `undefined`, in which * case you should respond with 406 "Not Acceptable". * * The `type` value may be a single mime type string * such as "application/json", the extension name * such as "json" or an array `["json", "html", "text/plain"]`. When a list * or array is given the _best_ match, if any is returned. * * Examples: * * // Accept: text/html * this.types('html'); * // => "html" * * // Accept: text/*, application/json * this.types('html'); * // => "html" * this.types('text/html'); * // => "text/html" * this.types('json', 'text'); * // => "json" * this.types('application/json'); * // => "application/json" * * // Accept: text/*, application/json * this.types('image/png'); * this.types('png'); * // => undefined * * // Accept: text/*;q=.5, application/json * this.types(['html', 'json']); * this.types('html', 'json'); * // => "json" * * @param {String|Array} types... * @return {String|Array|Boolean} * @public */ Accepts.prototype.type = Accepts.prototype.types = function (types_) { var types = types_; // support flattened arguments if (types && !Array.isArray(types)) { types = new Array(arguments.length); for (var i = 0; i < types.length; i++) { types[i] = arguments[i]; } } // no types, return all requested types if (!types || types.length === 0) { return this.negotiator.mediaTypes() } // no accept header, return first given type if (!this.headers.accept) { return types[0] } var mimes = types.map(extToMime); var accepts = this.negotiator.mediaTypes(mimes.filter(validMime)); var first = accepts[0]; return first ? types[mimes.indexOf(first)] : false }; /** * Return accepted encodings or best fit based on `encodings`. * * Given `Accept-Encoding: gzip, deflate` * an array sorted by quality is returned: * * ['gzip', 'deflate'] * * @param {String|Array} encodings... * @return {String|Array} * @public */ Accepts.prototype.encoding = Accepts.prototype.encodings = function (encodings_) { var encodings = encodings_; // support flattened arguments if (encodings && !Array.isArray(encodings)) { encodings = new Array(arguments.length); for (var i = 0; i < encodings.length; i++) { encodings[i] = arguments[i]; } } // no encodings, return all requested encodings if (!encodings || encodings.length === 0) { return this.negotiator.encodings() } return this.negotiator.encodings(encodings)[0] || false }; /** * Return accepted charsets or best fit based on `charsets`. * * Given `Accept-Charset: utf-8, iso-8859-1;q=0.2, utf-7;q=0.5` * an array sorted by quality is returned: * * ['utf-8', 'utf-7', 'iso-8859-1'] * * @param {String|Array} charsets... * @return {String|Array} * @public */ Accepts.prototype.charset = Accepts.prototype.charsets = function (charsets_) { var charsets = charsets_; // support flattened arguments if (charsets && !Array.isArray(charsets)) { charsets = new Array(arguments.length); for (var i = 0; i < charsets.length; i++) { charsets[i] = arguments[i]; } } // no charsets, return all requested charsets if (!charsets || charsets.length === 0) { return this.negotiator.charsets() } return this.negotiator.charsets(charsets)[0] || false }; /** * Return accepted languages or best fit based on `langs`. * * Given `Accept-Language: en;q=0.8, es, pt` * an array sorted by quality is returned: * * ['es', 'pt', 'en'] * * @param {String|Array} langs... * @return {Array|String} * @public */ Accepts.prototype.lang = Accepts.prototype.langs = Accepts.prototype.language = Accepts.prototype.languages = function (languages_) { var languages = languages_; // support flattened arguments if (languages && !Array.isArray(languages)) { languages = new Array(arguments.length); for (var i = 0; i < languages.length; i++) { languages[i] = arguments[i]; } } // no languages, return all requested languages if (!languages || languages.length === 0) { return this.negotiator.languages() } return this.negotiator.languages(languages)[0] || false }; /** * Convert extnames to mime. * * @param {String} type * @return {String} * @private */ function extToMime (type) { return type.indexOf('/') === -1 ? mime$1.lookup(type) : type } /** * Check if mime is valid. * * @param {String} type * @return {String} * @private */ function validMime (type) { return typeof type === 'string' } /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. * @private */ var accepts = accepts$1; var deprecate$1 = depd_1('express'); var isIP = require$$4__default['default'].isIP; var typeis = typeIs.exports; var http$2 = require$$0__default$3['default']; var fresh = fresh_1; var parseRange = rangeParser_1; var parse$6 = parseurl$1.exports; var proxyaddr = proxyAddr.exports; /** * Request prototype. * @public */ var req = Object.create(http$2.IncomingMessage.prototype); /** * Module exports. * @public */ var request = req; /** * Return request header. * * The `Referrer` header field is special-cased, * both `Referrer` and `Referer` are interchangeable. * * Examples: * * req.get('Content-Type'); * // => "text/plain" * * req.get('content-type'); * // => "text/plain" * * req.get('Something'); * // => undefined * * Aliased as `req.header()`. * * @param {String} name * @return {String} * @public */ req.get = req.header = function header(name) { if (!name) { throw new TypeError('name argument is required to req.get'); } if (typeof name !== 'string') { throw new TypeError('name must be a string to req.get'); } var lc = name.toLowerCase(); switch (lc) { case 'referer': case 'referrer': return this.headers.referrer || this.headers.referer; default: return this.headers[lc]; } }; /** * To do: update docs. * * Check if the given `type(s)` is acceptable, returning * the best match when true, otherwise `undefined`, in which * case you should respond with 406 "Not Acceptable". * * The `type` value may be a single MIME type string * such as "application/json", an extension name * such as "json", a comma-delimited list such as "json, html, text/plain", * an argument list such as `"json", "html", "text/plain"`, * or an array `["json", "html", "text/plain"]`. When a list * or array is given, the _best_ match, if any is returned. * * Examples: * * // Accept: text/html * req.accepts('html'); * // => "html" * * // Accept: text/*, application/json * req.accepts('html'); * // => "html" * req.accepts('text/html'); * // => "text/html" * req.accepts('json, text'); * // => "json" * req.accepts('application/json'); * // => "application/json" * * // Accept: text/*, application/json * req.accepts('image/png'); * req.accepts('png'); * // => undefined * * // Accept: text/*;q=.5, application/json * req.accepts(['html', 'json']); * req.accepts('html', 'json'); * req.accepts('html, json'); * // => "json" * * @param {String|Array} type(s) * @return {String|Array|Boolean} * @public */ req.accepts = function(){ var accept = accepts(this); return accept.types.apply(accept, arguments); }; /** * Check if the given `encoding`s are accepted. * * @param {String} ...encoding * @return {String|Array} * @public */ req.acceptsEncodings = function(){ var accept = accepts(this); return accept.encodings.apply(accept, arguments); }; req.acceptsEncoding = deprecate$1.function(req.acceptsEncodings, 'req.acceptsEncoding: Use acceptsEncodings instead'); /** * Check if the given `charset`s are acceptable, * otherwise you should respond with 406 "Not Acceptable". * * @param {String} ...charset * @return {String|Array} * @public */ req.acceptsCharsets = function(){ var accept = accepts(this); return accept.charsets.apply(accept, arguments); }; req.acceptsCharset = deprecate$1.function(req.acceptsCharsets, 'req.acceptsCharset: Use acceptsCharsets instead'); /** * Check if the given `lang`s are acceptable, * otherwise you should respond with 406 "Not Acceptable". * * @param {String} ...lang * @return {String|Array} * @public */ req.acceptsLanguages = function(){ var accept = accepts(this); return accept.languages.apply(accept, arguments); }; req.acceptsLanguage = deprecate$1.function(req.acceptsLanguages, 'req.acceptsLanguage: Use acceptsLanguages instead'); /** * Parse Range header field, capping to the given `size`. * * Unspecified ranges such as "0-" require knowledge of your resource length. In * the case of a byte range this is of course the total number of bytes. If the * Range header field is not given `undefined` is returned, `-1` when unsatisfiable, * and `-2` when syntactically invalid. * * When ranges are returned, the array has a "type" property which is the type of * range that is required (most commonly, "bytes"). Each array element is an object * with a "start" and "end" property for the portion of the range. * * The "combine" option can be set to `true` and overlapping & adjacent ranges * will be combined into a single range. * * NOTE: remember that ranges are inclusive, so for example "Range: users=0-3" * should respond with 4 users when available, not 3. * * @param {number} size * @param {object} [options] * @param {boolean} [options.combine=false] * @return {number|array} * @public */ req.range = function range(size, options) { var range = this.get('Range'); if (!range) return; return parseRange(size, range, options); }; /** * Return the value of param `name` when present or `defaultValue`. * * - Checks route placeholders, ex: _/user/:id_ * - Checks body params, ex: id=12, {"id":12} * - Checks query string params, ex: ?id=12 * * To utilize request bodies, `req.body` * should be an object. This can be done by using * the `bodyParser()` middleware. * * @param {String} name * @param {Mixed} [defaultValue] * @return {String} * @public */ req.param = function param(name, defaultValue) { var params = this.params || {}; var body = this.body || {}; var query = this.query || {}; var args = arguments.length === 1 ? 'name' : 'name, default'; deprecate$1('req.param(' + args + '): Use req.params, req.body, or req.query instead'); if (null != params[name] && params.hasOwnProperty(name)) return params[name]; if (null != body[name]) return body[name]; if (null != query[name]) return query[name]; return defaultValue; }; /** * Check if the incoming request contains the "Content-Type" * header field, and it contains the give mime `type`. * * Examples: * * // With Content-Type: text/html; charset=utf-8 * req.is('html'); * req.is('text/html'); * req.is('text/*'); * // => true * * // When Content-Type is application/json * req.is('json'); * req.is('application/json'); * req.is('application/*'); * // => true * * req.is('html'); * // => false * * @param {String|Array} types... * @return {String|false|null} * @public */ req.is = function is(types) { var arr = types; // support flattened arguments if (!Array.isArray(types)) { arr = new Array(arguments.length); for (var i = 0; i < arr.length; i++) { arr[i] = arguments[i]; } } return typeis(this, arr); }; /** * Return the protocol string "http" or "https" * when requested with TLS. When the "trust proxy" * setting trusts the socket address, the * "X-Forwarded-Proto" header field will be trusted * and used if present. * * If you're running behind a reverse proxy that * supplies https for you this may be enabled. * * @return {String} * @public */ defineGetter(req, 'protocol', function protocol(){ var proto = this.connection.encrypted ? 'https' : 'http'; var trust = this.app.get('trust proxy fn'); if (!trust(this.connection.remoteAddress, 0)) { return proto; } // Note: X-Forwarded-Proto is normally only ever a // single value, but this is to be safe. var header = this.get('X-Forwarded-Proto') || proto; var index = header.indexOf(','); return index !== -1 ? header.substring(0, index).trim() : header.trim() }); /** * Short-hand for: * * req.protocol === 'https' * * @return {Boolean} * @public */ defineGetter(req, 'secure', function secure(){ return this.protocol === 'https'; }); /** * Return the remote address from the trusted proxy. * * The is the remote address on the socket unless * "trust proxy" is set. * * @return {String} * @public */ defineGetter(req, 'ip', function ip(){ var trust = this.app.get('trust proxy fn'); return proxyaddr(this, trust); }); /** * When "trust proxy" is set, trusted proxy addresses + client. * * For example if the value were "client, proxy1, proxy2" * you would receive the array `["client", "proxy1", "proxy2"]` * where "proxy2" is the furthest down-stream and "proxy1" and * "proxy2" were trusted. * * @return {Array} * @public */ defineGetter(req, 'ips', function ips() { var trust = this.app.get('trust proxy fn'); var addrs = proxyaddr.all(this, trust); // reverse the order (to farthest -> closest) // and remove socket address addrs.reverse().pop(); return addrs }); /** * Return subdomains as an array. * * Subdomains are the dot-separated parts of the host before the main domain of * the app. By default, the domain of the app is assumed to be the last two * parts of the host. This can be changed by setting "subdomain offset". * * For example, if the domain is "tobi.ferrets.example.com": * If "subdomain offset" is not set, req.subdomains is `["ferrets", "tobi"]`. * If "subdomain offset" is 3, req.subdomains is `["tobi"]`. * * @return {Array} * @public */ defineGetter(req, 'subdomains', function subdomains() { var hostname = this.hostname; if (!hostname) return []; var offset = this.app.get('subdomain offset'); var subdomains = !isIP(hostname) ? hostname.split('.').reverse() : [hostname]; return subdomains.slice(offset); }); /** * Short-hand for `url.parse(req.url).pathname`. * * @return {String} * @public */ defineGetter(req, 'path', function path() { return parse$6(this).pathname; }); /** * Parse the "Host" header field to a hostname. * * When the "trust proxy" setting trusts the socket * address, the "X-Forwarded-Host" header field will * be trusted. * * @return {String} * @public */ defineGetter(req, 'hostname', function hostname(){ var trust = this.app.get('trust proxy fn'); var host = this.get('X-Forwarded-Host'); if (!host || !trust(this.connection.remoteAddress, 0)) { host = this.get('Host'); } else if (host.indexOf(',') !== -1) { // Note: X-Forwarded-Host is normally only ever a // single value, but this is to be safe. host = host.substring(0, host.indexOf(',')).trimRight(); } if (!host) return; // IPv6 literal support var offset = host[0] === '[' ? host.indexOf(']') + 1 : 0; var index = host.indexOf(':', offset); return index !== -1 ? host.substring(0, index) : host; }); // TODO: change req.host to return host in next major defineGetter(req, 'host', deprecate$1.function(function host(){ return this.hostname; }, 'req.host: Use req.hostname instead')); /** * Check if the request is fresh, aka * Last-Modified and/or the ETag * still match. * * @return {Boolean} * @public */ defineGetter(req, 'fresh', function(){ var method = this.method; var res = this.res; var status = res.statusCode; // GET or HEAD for weak freshness validation only if ('GET' !== method && 'HEAD' !== method) return false; // 2xx or 304 as per rfc2616 14.26 if ((status >= 200 && status < 300) || 304 === status) { return fresh(this.headers, { 'etag': res.get('ETag'), 'last-modified': res.get('Last-Modified') }) } return false; }); /** * Check if the request is stale, aka * "Last-Modified" and / or the "ETag" for the * resource has changed. * * @return {Boolean} * @public */ defineGetter(req, 'stale', function stale(){ return !this.fresh; }); /** * Check if the request was an _XMLHttpRequest_. * * @return {Boolean} * @public */ defineGetter(req, 'xhr', function xhr(){ var val = this.get('X-Requested-With') || ''; return val.toLowerCase() === 'xmlhttprequest'; }); /** * Helper function for creating a getter on an object. * * @param {Object} obj * @param {String} name * @param {Function} getter * @private */ function defineGetter(obj, name, getter) { Object.defineProperty(obj, name, { configurable: true, enumerable: true, get: getter }); } var cookieSignature = {}; /** * Module dependencies. */ (function (exports) { var crypto = require$$0__default$7['default']; /** * Sign the given `val` with `secret`. * * @param {String} val * @param {String} secret * @return {String} * @api private */ exports.sign = function(val, secret){ if ('string' != typeof val) throw new TypeError("Cookie value must be provided as a string."); if ('string' != typeof secret) throw new TypeError("Secret string must be provided."); return val + '.' + crypto .createHmac('sha256', secret) .update(val) .digest('base64') .replace(/\=+$/, ''); }; /** * Unsign and decode the given `val` with `secret`, * returning `false` if the signature is invalid. * * @param {String} val * @param {String} secret * @return {String|Boolean} * @api private */ exports.unsign = function(val, secret){ if ('string' != typeof val) throw new TypeError("Signed cookie string must be provided."); if ('string' != typeof secret) throw new TypeError("Secret string must be provided."); var str = val.slice(0, val.lastIndexOf('.')) , mac = exports.sign(str, secret); return sha1(mac) == sha1(val) ? str : false; }; /** * Private */ function sha1(str){ return crypto.createHash('sha1').update(str).digest('hex'); } }(cookieSignature)); var cookie$1 = {}; /*! * cookie * Copyright(c) 2012-2014 Roman Shtylman * Copyright(c) 2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. * @public */ cookie$1.parse = parse$5; cookie$1.serialize = serialize; /** * Module variables. * @private */ var decode = decodeURIComponent; var encode = encodeURIComponent; var pairSplitRegExp = /; */; /** * RegExp to match field-content in RFC 7230 sec 3.2 * * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] * field-vchar = VCHAR / obs-text * obs-text = %x80-FF */ var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; /** * Parse a cookie header. * * Parse the given cookie header string into an object * The object has the various cookies as keys(names) => values * * @param {string} str * @param {object} [options] * @return {object} * @public */ function parse$5(str, options) { if (typeof str !== 'string') { throw new TypeError('argument str must be a string'); } var obj = {}; var opt = options || {}; var pairs = str.split(pairSplitRegExp); var dec = opt.decode || decode; for (var i = 0; i < pairs.length; i++) { var pair = pairs[i]; var eq_idx = pair.indexOf('='); // skip things that don't look like key=value if (eq_idx < 0) { continue; } var key = pair.substr(0, eq_idx).trim(); var val = pair.substr(++eq_idx, pair.length).trim(); // quoted values if ('"' == val[0]) { val = val.slice(1, -1); } // only assign once if (undefined == obj[key]) { obj[key] = tryDecode(val, dec); } } return obj; } /** * Serialize data into a cookie header. * * Serialize the a name value pair into a cookie string suitable for * http headers. An optional options object specified cookie parameters. * * serialize('foo', 'bar', { httpOnly: true }) * => "foo=bar; httpOnly" * * @param {string} name * @param {string} val * @param {object} [options] * @return {string} * @public */ function serialize(name, val, options) { var opt = options || {}; var enc = opt.encode || encode; if (typeof enc !== 'function') { throw new TypeError('option encode is invalid'); } if (!fieldContentRegExp.test(name)) { throw new TypeError('argument name is invalid'); } var value = enc(val); if (value && !fieldContentRegExp.test(value)) { throw new TypeError('argument val is invalid'); } var str = name + '=' + value; if (null != opt.maxAge) { var maxAge = opt.maxAge - 0; if (isNaN(maxAge)) throw new Error('maxAge should be a Number'); str += '; Max-Age=' + Math.floor(maxAge); } if (opt.domain) { if (!fieldContentRegExp.test(opt.domain)) { throw new TypeError('option domain is invalid'); } str += '; Domain=' + opt.domain; } if (opt.path) { if (!fieldContentRegExp.test(opt.path)) { throw new TypeError('option path is invalid'); } str += '; Path=' + opt.path; } if (opt.expires) { if (typeof opt.expires.toUTCString !== 'function') { throw new TypeError('option expires is invalid'); } str += '; Expires=' + opt.expires.toUTCString(); } if (opt.httpOnly) { str += '; HttpOnly'; } if (opt.secure) { str += '; Secure'; } if (opt.sameSite) { var sameSite = typeof opt.sameSite === 'string' ? opt.sameSite.toLowerCase() : opt.sameSite; switch (sameSite) { case true: str += '; SameSite=Strict'; break; case 'lax': str += '; SameSite=Lax'; break; case 'strict': str += '; SameSite=Strict'; break; case 'none': str += '; SameSite=None'; break; default: throw new TypeError('option sameSite is invalid'); } } return str; } /** * Try decoding a string using a decoding function. * * @param {string} str * @param {function} decode * @private */ function tryDecode(str, decode) { try { return decode(str); } catch (e) { return str; } } var vary$2 = {exports: {}}; /*! * vary * Copyright(c) 2014-2017 Douglas Christopher Wilson * MIT Licensed */ /** * Module exports. */ vary$2.exports = vary$1; vary$2.exports.append = append$1; /** * RegExp to match field-name in RFC 7230 sec 3.2 * * field-name = token * token = 1*tchar * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" * / DIGIT / ALPHA * ; any VCHAR, except delimiters */ var FIELD_NAME_REGEXP = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; /** * Append a field to a vary header. * * @param {String} header * @param {String|Array} field * @return {String} * @public */ function append$1 (header, field) { if (typeof header !== 'string') { throw new TypeError('header argument is required') } if (!field) { throw new TypeError('field argument is required') } // get fields array var fields = !Array.isArray(field) ? parse$4(String(field)) : field; // assert on invalid field names for (var j = 0; j < fields.length; j++) { if (!FIELD_NAME_REGEXP.test(fields[j])) { throw new TypeError('field argument contains an invalid header name') } } // existing, unspecified vary if (header === '*') { return header } // enumerate current values var val = header; var vals = parse$4(header.toLowerCase()); // unspecified vary if (fields.indexOf('*') !== -1 || vals.indexOf('*') !== -1) { return '*' } for (var i = 0; i < fields.length; i++) { var fld = fields[i].toLowerCase(); // append value (case-preserving) if (vals.indexOf(fld) === -1) { vals.push(fld); val = val ? val + ', ' + fields[i] : fields[i]; } } return val } /** * Parse a vary header into an array. * * @param {String} header * @return {Array} * @private */ function parse$4 (header) { var end = 0; var list = []; var start = 0; // gather tokens for (var i = 0, len = header.length; i < len; i++) { switch (header.charCodeAt(i)) { case 0x20: /* */ if (start === end) { start = end = i + 1; } break case 0x2c: /* , */ list.push(header.substring(start, end)); start = end = i + 1; break default: end = i + 1; break } } // final token list.push(header.substring(start, end)); return list } /** * Mark that a request is varied on a header field. * * @param {Object} res * @param {String|Array} field * @public */ function vary$1 (res, field) { if (!res || !res.getHeader || !res.setHeader) { // quack quack throw new TypeError('res argument is required') } // get existing header var val = res.getHeader('Vary') || ''; var header = Array.isArray(val) ? val.join(', ') : String(val); // set new header if ((val = append$1(header, field))) { res.setHeader('Vary', val); } } /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. * @private */ var Buffer$1 = safeBuffer.exports.Buffer; var contentDisposition = contentDisposition$2.exports; var deprecate = depd_1('express'); var encodeUrl$1 = encodeurl; var escapeHtml$1 = escapeHtml_1; var http$1 = require$$0__default$3['default']; var isAbsolute = utils$9.isAbsolute; var onFinished = onFinished$5.exports; var path$2 = require$$0__default$5['default']; var statuses = statuses$3; var merge = utilsMerge.exports; var sign = cookieSignature.sign; var normalizeType = utils$9.normalizeType; var normalizeTypes = utils$9.normalizeTypes; var setCharset = utils$9.setCharset; var cookie = cookie$1; var send$1 = send$3.exports; var extname = path$2.extname; var mime = send$1.mime; var resolve$1 = path$2.resolve; var vary = vary$2.exports; /** * Response prototype. * @public */ var res = Object.create(http$1.ServerResponse.prototype); /** * Module exports. * @public */ var response = res; /** * Module variables. * @private */ var charsetRegExp = /;\s*charset\s*=/; /** * Set status `code`. * * @param {Number} code * @return {ServerResponse} * @public */ res.status = function status(code) { this.statusCode = code; return this; }; /** * Set Link header field with the given `links`. * * Examples: * * res.links({ * next: 'http://api.example.com/users?page=2', * last: 'http://api.example.com/users?page=5' * }); * * @param {Object} links * @return {ServerResponse} * @public */ res.links = function(links){ var link = this.get('Link') || ''; if (link) link += ', '; return this.set('Link', link + Object.keys(links).map(function(rel){ return '<' + links[rel] + '>; rel="' + rel + '"'; }).join(', ')); }; /** * Send a response. * * Examples: * * res.send(Buffer.from('wahoo')); * res.send({ some: 'json' }); * res.send('
some html
'); * * @param {string|number|boolean|object|Buffer} body * @public */ res.send = function send(body) { var chunk = body; var encoding; var req = this.req; var type; // settings var app = this.app; // allow status / body if (arguments.length === 2) { // res.send(body, status) backwards compat if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') { deprecate('res.send(body, status): Use res.status(status).send(body) instead'); this.statusCode = arguments[1]; } else { deprecate('res.send(status, body): Use res.status(status).send(body) instead'); this.statusCode = arguments[0]; chunk = arguments[1]; } } // disambiguate res.send(status) and res.send(status, num) if (typeof chunk === 'number' && arguments.length === 1) { // res.send(status) will set status message as text string if (!this.get('Content-Type')) { this.type('txt'); } deprecate('res.send(status): Use res.sendStatus(status) instead'); this.statusCode = chunk; chunk = statuses[chunk]; } switch (typeof chunk) { // string defaulting to html case 'string': if (!this.get('Content-Type')) { this.type('html'); } break; case 'boolean': case 'number': case 'object': if (chunk === null) { chunk = ''; } else if (Buffer$1.isBuffer(chunk)) { if (!this.get('Content-Type')) { this.type('bin'); } } else { return this.json(chunk); } break; } // write strings in utf-8 if (typeof chunk === 'string') { encoding = 'utf8'; type = this.get('Content-Type'); // reflect this in content-type if (typeof type === 'string') { this.set('Content-Type', setCharset(type, 'utf-8')); } } // determine if ETag should be generated var etagFn = app.get('etag fn'); var generateETag = !this.get('ETag') && typeof etagFn === 'function'; // populate Content-Length var len; if (chunk !== undefined) { if (Buffer$1.isBuffer(chunk)) { // get length of Buffer len = chunk.length; } else if (!generateETag && chunk.length < 1000) { // just calculate length when no ETag + small chunk len = Buffer$1.byteLength(chunk, encoding); } else { // convert chunk to Buffer and calculate chunk = Buffer$1.from(chunk, encoding); encoding = undefined; len = chunk.length; } this.set('Content-Length', len); } // populate ETag var etag; if (generateETag && len !== undefined) { if ((etag = etagFn(chunk, encoding))) { this.set('ETag', etag); } } // freshness if (req.fresh) this.statusCode = 304; // strip irrelevant headers if (204 === this.statusCode || 304 === this.statusCode) { this.removeHeader('Content-Type'); this.removeHeader('Content-Length'); this.removeHeader('Transfer-Encoding'); chunk = ''; } if (req.method === 'HEAD') { // skip body for HEAD this.end(); } else { // respond this.end(chunk, encoding); } return this; }; /** * Send JSON response. * * Examples: * * res.json(null); * res.json({ user: 'tj' }); * * @param {string|number|boolean|object} obj * @public */ res.json = function json(obj) { var val = obj; // allow status / body if (arguments.length === 2) { // res.json(body, status) backwards compat if (typeof arguments[1] === 'number') { deprecate('res.json(obj, status): Use res.status(status).json(obj) instead'); this.statusCode = arguments[1]; } else { deprecate('res.json(status, obj): Use res.status(status).json(obj) instead'); this.statusCode = arguments[0]; val = arguments[1]; } } // settings var app = this.app; var escape = app.get('json escape'); var replacer = app.get('json replacer'); var spaces = app.get('json spaces'); var body = stringify$5(val, replacer, spaces, escape); // content-type if (!this.get('Content-Type')) { this.set('Content-Type', 'application/json'); } return this.send(body); }; /** * Send JSON response with JSONP callback support. * * Examples: * * res.jsonp(null); * res.jsonp({ user: 'tj' }); * * @param {string|number|boolean|object} obj * @public */ res.jsonp = function jsonp(obj) { var val = obj; // allow status / body if (arguments.length === 2) { // res.json(body, status) backwards compat if (typeof arguments[1] === 'number') { deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead'); this.statusCode = arguments[1]; } else { deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead'); this.statusCode = arguments[0]; val = arguments[1]; } } // settings var app = this.app; var escape = app.get('json escape'); var replacer = app.get('json replacer'); var spaces = app.get('json spaces'); var body = stringify$5(val, replacer, spaces, escape); var callback = this.req.query[app.get('jsonp callback name')]; // content-type if (!this.get('Content-Type')) { this.set('X-Content-Type-Options', 'nosniff'); this.set('Content-Type', 'application/json'); } // fixup callback if (Array.isArray(callback)) { callback = callback[0]; } // jsonp if (typeof callback === 'string' && callback.length !== 0) { this.set('X-Content-Type-Options', 'nosniff'); this.set('Content-Type', 'text/javascript'); // restrict callback charset callback = callback.replace(/[^\[\]\w$.]/g, ''); // replace chars not allowed in JavaScript that are in JSON body = body .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029'); // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse" // the typeof check is just to reduce client error noise body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');'; } return this.send(body); }; /** * Send given HTTP status code. * * Sets the response status to `statusCode` and the body of the * response to the standard description from node's http.STATUS_CODES * or the statusCode number if no description. * * Examples: * * res.sendStatus(200); * * @param {number} statusCode * @public */ res.sendStatus = function sendStatus(statusCode) { var body = statuses[statusCode] || String(statusCode); this.statusCode = statusCode; this.type('txt'); return this.send(body); }; /** * Transfer the file at the given `path`. * * Automatically sets the _Content-Type_ response header field. * The callback `callback(err)` is invoked when the transfer is complete * or when an error occurs. Be sure to check `res.sentHeader` * if you wish to attempt responding, as the header and some data * may have already been transferred. * * Options: * * - `maxAge` defaulting to 0 (can be string converted by `ms`) * - `root` root directory for relative filenames * - `headers` object of headers to serve with file * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them * * Other options are passed along to `send`. * * Examples: * * The following example illustrates how `res.sendFile()` may * be used as an alternative for the `static()` middleware for * dynamic situations. The code backing `res.sendFile()` is actually * the same code, so HTTP cache support etc is identical. * * app.get('/user/:uid/photos/:file', function(req, res){ * var uid = req.params.uid * , file = req.params.file; * * req.user.mayViewFilesFrom(uid, function(yes){ * if (yes) { * res.sendFile('/uploads/' + uid + '/' + file); * } else { * res.send(403, 'Sorry! you cant see that.'); * } * }); * }); * * @public */ res.sendFile = function sendFile(path, options, callback) { var done = callback; var req = this.req; var res = this; var next = req.next; var opts = options || {}; if (!path) { throw new TypeError('path argument is required to res.sendFile'); } if (typeof path !== 'string') { throw new TypeError('path must be a string to res.sendFile') } // support function as second arg if (typeof options === 'function') { done = options; opts = {}; } if (!opts.root && !isAbsolute(path)) { throw new TypeError('path must be absolute or specify root to res.sendFile'); } // create file stream var pathname = encodeURI(path); var file = send$1(req, pathname, opts); // transfer sendfile(res, file, opts, function (err) { if (done) return done(err); if (err && err.code === 'EISDIR') return next(); // next() all but write errors if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { next(err); } }); }; /** * Transfer the file at the given `path`. * * Automatically sets the _Content-Type_ response header field. * The callback `callback(err)` is invoked when the transfer is complete * or when an error occurs. Be sure to check `res.sentHeader` * if you wish to attempt responding, as the header and some data * may have already been transferred. * * Options: * * - `maxAge` defaulting to 0 (can be string converted by `ms`) * - `root` root directory for relative filenames * - `headers` object of headers to serve with file * - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them * * Other options are passed along to `send`. * * Examples: * * The following example illustrates how `res.sendfile()` may * be used as an alternative for the `static()` middleware for * dynamic situations. The code backing `res.sendfile()` is actually * the same code, so HTTP cache support etc is identical. * * app.get('/user/:uid/photos/:file', function(req, res){ * var uid = req.params.uid * , file = req.params.file; * * req.user.mayViewFilesFrom(uid, function(yes){ * if (yes) { * res.sendfile('/uploads/' + uid + '/' + file); * } else { * res.send(403, 'Sorry! you cant see that.'); * } * }); * }); * * @public */ res.sendfile = function (path, options, callback) { var done = callback; var req = this.req; var res = this; var next = req.next; var opts = options || {}; // support function as second arg if (typeof options === 'function') { done = options; opts = {}; } // create file stream var file = send$1(req, path, opts); // transfer sendfile(res, file, opts, function (err) { if (done) return done(err); if (err && err.code === 'EISDIR') return next(); // next() all but write errors if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { next(err); } }); }; res.sendfile = deprecate.function(res.sendfile, 'res.sendfile: Use res.sendFile instead'); /** * Transfer the file at the given `path` as an attachment. * * Optionally providing an alternate attachment `filename`, * and optional callback `callback(err)`. The callback is invoked * when the data transfer is complete, or when an error has * ocurred. Be sure to check `res.headersSent` if you plan to respond. * * Optionally providing an `options` object to use with `res.sendFile()`. * This function will set the `Content-Disposition` header, overriding * any `Content-Disposition` header passed as header options in order * to set the attachment and filename. * * This method uses `res.sendFile()`. * * @public */ res.download = function download (path, filename, options, callback) { var done = callback; var name = filename; var opts = options || null; // support function as second or third arg if (typeof filename === 'function') { done = filename; name = null; opts = null; } else if (typeof options === 'function') { done = options; opts = null; } // set Content-Disposition when file is sent var headers = { 'Content-Disposition': contentDisposition(name || path) }; // merge user-provided headers if (opts && opts.headers) { var keys = Object.keys(opts.headers); for (var i = 0; i < keys.length; i++) { var key = keys[i]; if (key.toLowerCase() !== 'content-disposition') { headers[key] = opts.headers[key]; } } } // merge user-provided options opts = Object.create(opts); opts.headers = headers; // Resolve the full path for sendFile var fullPath = resolve$1(path); // send file return this.sendFile(fullPath, opts, done) }; /** * Set _Content-Type_ response header with `type` through `mime.lookup()` * when it does not contain "/", or set the Content-Type to `type` otherwise. * * Examples: * * res.type('.html'); * res.type('html'); * res.type('json'); * res.type('application/json'); * res.type('png'); * * @param {String} type * @return {ServerResponse} for chaining * @public */ res.contentType = res.type = function contentType(type) { var ct = type.indexOf('/') === -1 ? mime.lookup(type) : type; return this.set('Content-Type', ct); }; /** * Respond to the Acceptable formats using an `obj` * of mime-type callbacks. * * This method uses `req.accepted`, an array of * acceptable types ordered by their quality values. * When "Accept" is not present the _first_ callback * is invoked, otherwise the first match is used. When * no match is performed the server responds with * 406 "Not Acceptable". * * Content-Type is set for you, however if you choose * you may alter this within the callback using `res.type()` * or `res.set('Content-Type', ...)`. * * res.format({ * 'text/plain': function(){ * res.send('hey'); * }, * * 'text/html': function(){ * res.send('hey
'); * }, * * 'appliation/json': function(){ * res.send({ message: 'hey' }); * } * }); * * In addition to canonicalized MIME types you may * also use extnames mapped to these types: * * res.format({ * text: function(){ * res.send('hey'); * }, * * html: function(){ * res.send('hey
'); * }, * * json: function(){ * res.send({ message: 'hey' }); * } * }); * * By default Express passes an `Error` * with a `.status` of 406 to `next(err)` * if a match is not made. If you provide * a `.default` callback it will be invoked * instead. * * @param {Object} obj * @return {ServerResponse} for chaining * @public */ res.format = function(obj){ var req = this.req; var next = req.next; var fn = obj.default; if (fn) delete obj.default; var keys = Object.keys(obj); var key = keys.length > 0 ? req.accepts(keys) : false; this.vary("Accept"); if (key) { this.set('Content-Type', normalizeType(key).value); obj[key](req, this, next); } else if (fn) { fn(); } else { var err = new Error('Not Acceptable'); err.status = err.statusCode = 406; err.types = normalizeTypes(keys).map(function(o){ return o.value }); next(err); } return this; }; /** * Set _Content-Disposition_ header to _attachment_ with optional `filename`. * * @param {String} filename * @return {ServerResponse} * @public */ res.attachment = function attachment(filename) { if (filename) { this.type(extname(filename)); } this.set('Content-Disposition', contentDisposition(filename)); return this; }; /** * Append additional header `field` with value `val`. * * Example: * * res.append('Link', ['' + statuses[status] + '. Redirecting to ' + u + '
'; }, default: function(){ body = ''; } }); // Respond this.statusCode = status; this.set('Content-Length', Buffer$1.byteLength(body)); if (this.req.method === 'HEAD') { this.end(); } else { this.end(body); } }; /** * Add `field` to Vary. If already present in the Vary set, then * this call is simply ignored. * * @param {Array|String} field * @return {ServerResponse} for chaining * @public */ res.vary = function(field){ // checks for back-compat if (!field || (Array.isArray(field) && !field.length)) { deprecate('res.vary(): Provide a field name'); return this; } vary(this, field); return this; }; /** * Render `view` with the given `options` and optional callback `fn`. * When a callback function is given a response will _not_ be made * automatically, otherwise a response of _200_ and _text/html_ is given. * * Options: * * - `cache` boolean hinting to the engine it should cache * - `filename` filename of the view being rendered * * @public */ res.render = function render(view, options, callback) { var app = this.req.app; var done = callback; var opts = options || {}; var req = this.req; var self = this; // support callback function as second arg if (typeof options === 'function') { done = options; opts = {}; } // merge res.locals opts._locals = self.locals; // default callback to respond done = done || function (err, str) { if (err) return req.next(err); self.send(str); }; // render app.render(view, opts, done); }; // pipe the send file stream function sendfile(res, file, options, callback) { var done = false; var streaming; // request aborted function onaborted() { if (done) return; done = true; var err = new Error('Request aborted'); err.code = 'ECONNABORTED'; callback(err); } // directory function ondirectory() { if (done) return; done = true; var err = new Error('EISDIR, read'); err.code = 'EISDIR'; callback(err); } // errors function onerror(err) { if (done) return; done = true; callback(err); } // ended function onend() { if (done) return; done = true; callback(); } // file function onfile() { streaming = false; } // finished function onfinish(err) { if (err && err.code === 'ECONNRESET') return onaborted(); if (err) return onerror(err); if (done) return; setImmediate(function () { if (streaming !== false && !done) { onaborted(); return; } if (done) return; done = true; callback(); }); } // streaming function onstream() { streaming = true; } file.on('directory', ondirectory); file.on('end', onend); file.on('error', onerror); file.on('file', onfile); file.on('stream', onstream); onFinished(res, onfinish); if (options.headers) { // set headers on successful transfer file.on('headers', function headers(res) { var obj = options.headers; var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { var k = keys[i]; res.setHeader(k, obj[k]); } }); } // pipe file.pipe(res); } /** * Stringify JSON, like JSON.stringify, but v8 optimized, with the * ability to escape characters that can trigger HTML sniffing. * * @param {*} value * @param {function} replaces * @param {number} spaces * @param {boolean} escape * @returns {string} * @private */ function stringify$5 (value, replacer, spaces, escape) { // v8 checks arguments.length for optimizing simple call // https://bugs.chromium.org/p/v8/issues/detail?id=4730 var json = replacer || spaces ? JSON.stringify(value, replacer, spaces) : JSON.stringify(value); if (escape) { json = json.replace(/[<>&]/g, function (c) { switch (c.charCodeAt(0)) { case 0x3c: return '\\u003c' case 0x3e: return '\\u003e' case 0x26: return '\\u0026' /* istanbul ignore next: unreachable default */ default: return c } }); } return json } var serveStatic$1 = {exports: {}}; /*! * serve-static * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014-2016 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. * @private */ var encodeUrl = encodeurl; var escapeHtml = escapeHtml_1; var parseUrl = parseurl$1.exports; var resolve = require$$0__default$5['default'].resolve; var send = send$3.exports; var url$3 = require$$0__default$2['default']; /** * Module exports. * @public */ serveStatic$1.exports = serveStatic; serveStatic$1.exports.mime = send.mime; /** * @param {string} root * @param {object} [options] * @return {function} * @public */ function serveStatic (root, options) { if (!root) { throw new TypeError('root path required') } if (typeof root !== 'string') { throw new TypeError('root path must be a string') } // copy options object var opts = Object.create(options || null); // fall-though var fallthrough = opts.fallthrough !== false; // default redirect var redirect = opts.redirect !== false; // headers listener var setHeaders = opts.setHeaders; if (setHeaders && typeof setHeaders !== 'function') { throw new TypeError('option setHeaders must be function') } // setup options for send opts.maxage = opts.maxage || opts.maxAge || 0; opts.root = resolve(root); // construct directory listener var onDirectory = redirect ? createRedirectDirectoryListener() : createNotFoundDirectoryListener(); return function serveStatic (req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { if (fallthrough) { return next() } // method not allowed res.statusCode = 405; res.setHeader('Allow', 'GET, HEAD'); res.setHeader('Content-Length', '0'); res.end(); return } var forwardError = !fallthrough; var originalUrl = parseUrl.original(req); var path = parseUrl(req).pathname; // make sure redirect occurs at mount if (path === '/' && originalUrl.pathname.substr(-1) !== '/') { path = ''; } // create send stream var stream = send(req, path, opts); // add directory handler stream.on('directory', onDirectory); // add headers listener if (setHeaders) { stream.on('headers', setHeaders); } // add file listener for fallthrough if (fallthrough) { stream.on('file', function onFile () { // once file is determined, always forward error forwardError = true; }); } // forward errors stream.on('error', function error (err) { if (forwardError || !(err.statusCode < 500)) { next(err); return } next(); }); // pipe stream.pipe(res); } } /** * Collapse all leading slashes into a single slash * @private */ function collapseLeadingSlashes (str) { for (var i = 0; i < str.length; i++) { if (str.charCodeAt(i) !== 0x2f /* / */) { break } } return i > 1 ? '/' + str.substr(i) : str } /** * Create a minimal HTML document. * * @param {string} title * @param {string} body * @private */ function createHtmlDocument (title, body) { return '\n' + '\n' + '\n' + '\n' + '' + body + '\n' + '\n' + '\n' } /** * Create a directory listener that just 404s. * @private */ function createNotFoundDirectoryListener () { return function notFound () { this.error(404); } } /** * Create a directory listener that performs a redirect. * @private */ function createRedirectDirectoryListener () { return function redirect (res) { if (this.hasTrailingSlash()) { this.error(404); return } // get original URL var originalUrl = parseUrl.original(this.req); // append trailing slash originalUrl.path = null; originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/'); // reformat the URL var loc = encodeUrl(url$3.format(originalUrl)); var doc = createHtmlDocument('Redirecting', 'Redirecting to ' + escapeHtml(loc) + ''); // send redirect response res.statusCode = 301; res.setHeader('Content-Type', 'text/html; charset=UTF-8'); res.setHeader('Content-Length', Buffer.byteLength(doc)); res.setHeader('Content-Security-Policy', "default-src 'none'"); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Location', loc); res.end(doc); } } /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ (function (module, exports) { /** * Module dependencies. */ var bodyParser$1 = bodyParser.exports; var EventEmitter = require$$0__default$4['default'].EventEmitter; var mixin = mergeDescriptors; var proto = application.exports; var Route = route; var Router = router$1.exports; var req = request; var res = response; /** * Expose `createApplication()`. */ exports = module.exports = createApplication; /** * Create an express application. * * @return {Function} * @api public */ function createApplication() { var app = function(req, res, next) { app.handle(req, res, next); }; mixin(app, EventEmitter.prototype, false); mixin(app, proto, false); // expose the prototype that will get set on requests app.request = Object.create(req, { app: { configurable: true, enumerable: true, writable: true, value: app } }); // expose the prototype that will get set on responses app.response = Object.create(res, { app: { configurable: true, enumerable: true, writable: true, value: app } }); app.init(); return app; } /** * Expose the prototypes. */ exports.application = proto; exports.request = req; exports.response = res; /** * Expose constructors. */ exports.Route = Route; exports.Router = Router; /** * Expose middleware */ exports.json = bodyParser$1.json; exports.query = query; exports.raw = bodyParser$1.raw; exports.static = serveStatic$1.exports; exports.text = bodyParser$1.text; exports.urlencoded = bodyParser$1.urlencoded; /** * Replace removed middleware with an appropriate error message. */ var removedMiddlewares = [ 'bodyParser', 'compress', 'cookieSession', 'session', 'logger', 'cookieParser', 'favicon', 'responseTime', 'errorHandler', 'timeout', 'methodOverride', 'vhost', 'csrf', 'directory', 'limit', 'multipart', 'staticCache' ]; removedMiddlewares.forEach(function (name) { Object.defineProperty(exports, name, { get: function () { throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.'); }, configurable: true }); }); }(express$1, express$1.exports)); /*! * express * Copyright(c) 2009-2013 TJ Holowaychuk * Copyright(c) 2013 Roman Shtylman * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ var express = express$1.exports; var dist = {}; var httpProxyMiddleware = {}; var httpProxy$3 = {exports: {}}; var eventemitter3 = {exports: {}}; (function (module) { var has = Object.prototype.hasOwnProperty , prefix = '~'; /** * Constructor to create a storage for our `EE` objects. * An `Events` instance is a plain object whose properties are event names. * * @constructor * @private */ function Events() {} // // We try to not inherit from `Object.prototype`. In some engines creating an // instance in this way is faster than calling `Object.create(null)` directly. // If `Object.create(null)` is not supported we prefix the event names with a // character to make sure that the built-in object properties are not // overridden or used as an attack vector. // if (Object.create) { Events.prototype = Object.create(null); // // This hack is needed because the `__proto__` property is still inherited in // some old browsers like Android 4, iPhone 5.1, Opera 11 and Safari 5. // if (!new Events().__proto__) prefix = false; } /** * Representation of a single event listener. * * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} [once=false] Specify if the listener is a one-time listener. * @constructor * @private */ function EE(fn, context, once) { this.fn = fn; this.context = context; this.once = once || false; } /** * Add a listener for a given event. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} context The context to invoke the listener with. * @param {Boolean} once Specify if the listener is a one-time listener. * @returns {EventEmitter} * @private */ function addListener(emitter, event, fn, context, once) { if (typeof fn !== 'function') { throw new TypeError('The listener must be a function'); } var listener = new EE(fn, context || emitter, once) , evt = prefix ? prefix + event : event; if (!emitter._events[evt]) emitter._events[evt] = listener, emitter._eventsCount++; else if (!emitter._events[evt].fn) emitter._events[evt].push(listener); else emitter._events[evt] = [emitter._events[evt], listener]; return emitter; } /** * Clear event by name. * * @param {EventEmitter} emitter Reference to the `EventEmitter` instance. * @param {(String|Symbol)} evt The Event name. * @private */ function clearEvent(emitter, evt) { if (--emitter._eventsCount === 0) emitter._events = new Events(); else delete emitter._events[evt]; } /** * Minimal `EventEmitter` interface that is molded against the Node.js * `EventEmitter` interface. * * @constructor * @public */ function EventEmitter() { this._events = new Events(); this._eventsCount = 0; } /** * Return an array listing the events for which the emitter has registered * listeners. * * @returns {Array} * @public */ EventEmitter.prototype.eventNames = function eventNames() { var names = [] , events , name; if (this._eventsCount === 0) return names; for (name in (events = this._events)) { if (has.call(events, name)) names.push(prefix ? name.slice(1) : name); } if (Object.getOwnPropertySymbols) { return names.concat(Object.getOwnPropertySymbols(events)); } return names; }; /** * Return the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Array} The registered listeners. * @public */ EventEmitter.prototype.listeners = function listeners(event) { var evt = prefix ? prefix + event : event , handlers = this._events[evt]; if (!handlers) return []; if (handlers.fn) return [handlers.fn]; for (var i = 0, l = handlers.length, ee = new Array(l); i < l; i++) { ee[i] = handlers[i].fn; } return ee; }; /** * Return the number of listeners listening to a given event. * * @param {(String|Symbol)} event The event name. * @returns {Number} The number of listeners. * @public */ EventEmitter.prototype.listenerCount = function listenerCount(event) { var evt = prefix ? prefix + event : event , listeners = this._events[evt]; if (!listeners) return 0; if (listeners.fn) return 1; return listeners.length; }; /** * Calls each of the listeners registered for a given event. * * @param {(String|Symbol)} event The event name. * @returns {Boolean} `true` if the event had listeners, else `false`. * @public */ EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return false; var listeners = this._events[evt] , len = arguments.length , args , i; if (listeners.fn) { if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); switch (len) { case 1: return listeners.fn.call(listeners.context), true; case 2: return listeners.fn.call(listeners.context, a1), true; case 3: return listeners.fn.call(listeners.context, a1, a2), true; case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; } for (i = 1, args = new Array(len -1); i < len; i++) { args[i - 1] = arguments[i]; } listeners.fn.apply(listeners.context, args); } else { var length = listeners.length , j; for (i = 0; i < length; i++) { if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); switch (len) { case 1: listeners[i].fn.call(listeners[i].context); break; case 2: listeners[i].fn.call(listeners[i].context, a1); break; case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; case 4: listeners[i].fn.call(listeners[i].context, a1, a2, a3); break; default: if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { args[j - 1] = arguments[j]; } listeners[i].fn.apply(listeners[i].context, args); } } } return true; }; /** * Add a listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.on = function on(event, fn, context) { return addListener(this, event, fn, context, false); }; /** * Add a one-time listener for a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn The listener function. * @param {*} [context=this] The context to invoke the listener with. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.once = function once(event, fn, context) { return addListener(this, event, fn, context, true); }; /** * Remove the listeners of a given event. * * @param {(String|Symbol)} event The event name. * @param {Function} fn Only remove the listeners that match this function. * @param {*} context Only remove the listeners that have this context. * @param {Boolean} once Only remove one-time listeners. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { var evt = prefix ? prefix + event : event; if (!this._events[evt]) return this; if (!fn) { clearEvent(this, evt); return this; } var listeners = this._events[evt]; if (listeners.fn) { if ( listeners.fn === fn && (!once || listeners.once) && (!context || listeners.context === context) ) { clearEvent(this, evt); } } else { for (var i = 0, events = [], length = listeners.length; i < length; i++) { if ( listeners[i].fn !== fn || (once && !listeners[i].once) || (context && listeners[i].context !== context) ) { events.push(listeners[i]); } } // // Reset the array, or remove it completely if we have no more listeners. // if (events.length) this._events[evt] = events.length === 1 ? events[0] : events; else clearEvent(this, evt); } return this; }; /** * Remove all listeners, or those of the specified event. * * @param {(String|Symbol)} [event] The event name. * @returns {EventEmitter} `this`. * @public */ EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { var evt; if (event) { evt = prefix ? prefix + event : event; if (this._events[evt]) clearEvent(this, evt); } else { this._events = new Events(); this._eventsCount = 0; } return this; }; // // Alias methods names because people roll like that. // EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.addListener = EventEmitter.prototype.on; // // Expose the prefix. // EventEmitter.prefixed = prefix; // // Allow `EventEmitter` to be imported as module namespace. // EventEmitter.EventEmitter = EventEmitter; // // Expose the module. // { module.exports = EventEmitter; } }(eventemitter3)); var common$3 = {}; /** * Check if we're required to add a port number. * * @see https://url.spec.whatwg.org/#default-port * @param {Number|String} port Port number we need to check * @param {String} protocol Protocol we need to check against. * @returns {Boolean} Is it a default port for the given protocol * @api private */ var requiresPort = function required(port, protocol) { protocol = protocol.split(':')[0]; port = +port; if (!port) return false; switch (protocol) { case 'http': case 'ws': return port !== 80; case 'https': case 'wss': return port !== 443; case 'ftp': return port !== 21; case 'gopher': return port !== 70; case 'file': return false; } return port !== 0; }; (function (exports) { var common = exports, url = require$$0__default$2['default'], extend = require$$1__default['default']._extend, required = requiresPort; var upgradeHeader = /(^|,)\s*upgrade\s*($|,)/i, isSSL = /^https|wss/; /** * Simple Regex for testing if protocol is https */ common.isSSL = isSSL; /** * Copies the right headers from `options` and `req` to * `outgoing` which is then used to fire the proxied * request. * * Examples: * * common.setupOutgoing(outgoing, options, req) * // => { host: ..., hostname: ...} * * @param {Object} Outgoing Base object to be filled with required properties * @param {Object} Options Config object passed to the proxy * @param {ClientRequest} Req Request Object * @param {String} Forward String to select forward or target * * @return {Object} Outgoing Object with all required properties set * * @api private */ common.setupOutgoing = function(outgoing, options, req, forward) { outgoing.port = options[forward || 'target'].port || (isSSL.test(options[forward || 'target'].protocol) ? 443 : 80); ['host', 'hostname', 'socketPath', 'pfx', 'key', 'passphrase', 'cert', 'ca', 'ciphers', 'secureProtocol'].forEach( function(e) { outgoing[e] = options[forward || 'target'][e]; } ); outgoing.method = options.method || req.method; outgoing.headers = extend({}, req.headers); if (options.headers){ extend(outgoing.headers, options.headers); } if (options.auth) { outgoing.auth = options.auth; } if (options.ca) { outgoing.ca = options.ca; } if (isSSL.test(options[forward || 'target'].protocol)) { outgoing.rejectUnauthorized = (typeof options.secure === "undefined") ? true : options.secure; } outgoing.agent = options.agent || false; outgoing.localAddress = options.localAddress; // // Remark: If we are false and not upgrading, set the connection: close. This is the right thing to do // as node core doesn't handle this COMPLETELY properly yet. // if (!outgoing.agent) { outgoing.headers = outgoing.headers || {}; if (typeof outgoing.headers.connection !== 'string' || !upgradeHeader.test(outgoing.headers.connection) ) { outgoing.headers.connection = 'close'; } } // the final path is target path + relative path requested by user: var target = options[forward || 'target']; var targetPath = target && options.prependPath !== false ? (target.path || '') : ''; // // Remark: Can we somehow not use url.parse as a perf optimization? // var outgoingPath = !options.toProxy ? (url.parse(req.url).path || '') : req.url; // // Remark: ignorePath will just straight up ignore whatever the request's // path is. This can be labeled as FOOT-GUN material if you do not know what // you are doing and are using conflicting options. // outgoingPath = !options.ignorePath ? outgoingPath : ''; outgoing.path = common.urlJoin(targetPath, outgoingPath); if (options.changeOrigin) { outgoing.headers.host = required(outgoing.port, options[forward || 'target'].protocol) && !hasPort(outgoing.host) ? outgoing.host + ':' + outgoing.port : outgoing.host; } return outgoing; }; /** * Set the proper configuration for sockets, * set no delay and set keep alive, also set * the timeout to 0. * * Examples: * * common.setupSocket(socket) * // => Socket * * @param {Socket} Socket instance to setup * * @return {Socket} Return the configured socket. * * @api private */ common.setupSocket = function(socket) { socket.setTimeout(0); socket.setNoDelay(true); socket.setKeepAlive(true, 0); return socket; }; /** * Get the port number from the host. Or guess it based on the connection type. * * @param {Request} req Incoming HTTP request. * * @return {String} The port number. * * @api private */ common.getPort = function(req) { var res = req.headers.host ? req.headers.host.match(/:(\d+)/) : ''; return res ? res[1] : common.hasEncryptedConnection(req) ? '443' : '80'; }; /** * Check if the request has an encrypted connection. * * @param {Request} req Incoming HTTP request. * * @return {Boolean} Whether the connection is encrypted or not. * * @api private */ common.hasEncryptedConnection = function(req) { return Boolean(req.connection.encrypted || req.connection.pair); }; /** * OS-agnostic join (doesn't break on URLs like path.join does on Windows)> * * @return {String} The generated path. * * @api private */ common.urlJoin = function() { // // We do not want to mess with the query string. All we want to touch is the path. // var args = Array.prototype.slice.call(arguments), lastIndex = args.length - 1, last = args[lastIndex], lastSegs = last.split('?'), retSegs; args[lastIndex] = lastSegs.shift(); // // Join all strings, but remove empty strings so we don't get extra slashes from // joining e.g. ['', 'am'] // retSegs = [ args.filter(Boolean).join('/') .replace(/\/+/g, '/') .replace('http:/', 'http://') .replace('https:/', 'https://') ]; // Only join the query string if it exists so we don't have trailing a '?' // on every request // Handle case where there could be multiple ? in the URL. retSegs.push.apply(retSegs, lastSegs); return retSegs.join('?') }; /** * Rewrites or removes the domain of a cookie header * * @param {String|Array} Header * @param {Object} Config, mapping of domain to rewritten domain. * '*' key to match any domain, null value to remove the domain. * * @api private */ common.rewriteCookieProperty = function rewriteCookieProperty(header, config, property) { if (Array.isArray(header)) { return header.map(function (headerElement) { return rewriteCookieProperty(headerElement, config, property); }); } return header.replace(new RegExp("(;\\s*" + property + "=)([^;]+)", 'i'), function(match, prefix, previousValue) { var newValue; if (previousValue in config) { newValue = config[previousValue]; } else if ('*' in config) { newValue = config['*']; } else { //no match, return previous value return match; } if (newValue) { //replace value return prefix + newValue; } else { //remove value return ''; } }); }; /** * Check the host and see if it potentially has a port in it (keep it simple) * * @returns {Boolean} Whether we have one or not * * @api private */ function hasPort(host) { return !!~host.indexOf(':'); }}(common$3)); var url$2 = require$$0__default$2['default'], common$2 = common$3; var redirectRegex = /^201|30(1|2|7|8)$/; /*! * Array of passes. * * A `pass` is just a function that is executed on `req, res, options` * so that you can easily add new checks while still keeping the base * flexible. */ var webOutgoing = { // <-- /** * If is a HTTP 1.0 request, remove chunk headers * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {proxyResponse} Res Response object from the proxy request * * @api private */ removeChunked: function removeChunked(req, res, proxyRes) { if (req.httpVersion === '1.0') { delete proxyRes.headers['transfer-encoding']; } }, /** * If is a HTTP 1.0 request, set the correct connection header * or if connection header not present, then use `keep-alive` * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {proxyResponse} Res Response object from the proxy request * * @api private */ setConnection: function setConnection(req, res, proxyRes) { if (req.httpVersion === '1.0') { proxyRes.headers.connection = req.headers.connection || 'close'; } else if (req.httpVersion !== '2.0' && !proxyRes.headers.connection) { proxyRes.headers.connection = req.headers.connection || 'keep-alive'; } }, setRedirectHostRewrite: function setRedirectHostRewrite(req, res, proxyRes, options) { if ((options.hostRewrite || options.autoRewrite || options.protocolRewrite) && proxyRes.headers['location'] && redirectRegex.test(proxyRes.statusCode)) { var target = url$2.parse(options.target); var u = url$2.parse(proxyRes.headers['location']); // make sure the redirected host matches the target host before rewriting if (target.host != u.host) { return; } if (options.hostRewrite) { u.host = options.hostRewrite; } else if (options.autoRewrite) { u.host = req.headers['host']; } if (options.protocolRewrite) { u.protocol = options.protocolRewrite; } proxyRes.headers['location'] = u.format(); } }, /** * Copy headers from proxyResponse to response * set each header in response object. * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {proxyResponse} Res Response object from the proxy request * @param {Object} Options options.cookieDomainRewrite: Config to rewrite cookie domain * * @api private */ writeHeaders: function writeHeaders(req, res, proxyRes, options) { var rewriteCookieDomainConfig = options.cookieDomainRewrite, rewriteCookiePathConfig = options.cookiePathRewrite, preserveHeaderKeyCase = options.preserveHeaderKeyCase, rawHeaderKeyMap, setHeader = function(key, header) { if (header == undefined) return; if (rewriteCookieDomainConfig && key.toLowerCase() === 'set-cookie') { header = common$2.rewriteCookieProperty(header, rewriteCookieDomainConfig, 'domain'); } if (rewriteCookiePathConfig && key.toLowerCase() === 'set-cookie') { header = common$2.rewriteCookieProperty(header, rewriteCookiePathConfig, 'path'); } res.setHeader(String(key).trim(), header); }; if (typeof rewriteCookieDomainConfig === 'string') { //also test for '' rewriteCookieDomainConfig = { '*': rewriteCookieDomainConfig }; } if (typeof rewriteCookiePathConfig === 'string') { //also test for '' rewriteCookiePathConfig = { '*': rewriteCookiePathConfig }; } // message.rawHeaders is added in: v0.11.6 // https://nodejs.org/api/http.html#http_message_rawheaders if (preserveHeaderKeyCase && proxyRes.rawHeaders != undefined) { rawHeaderKeyMap = {}; for (var i = 0; i < proxyRes.rawHeaders.length; i += 2) { var key = proxyRes.rawHeaders[i]; rawHeaderKeyMap[key.toLowerCase()] = key; } } Object.keys(proxyRes.headers).forEach(function(key) { var header = proxyRes.headers[key]; if (preserveHeaderKeyCase && rawHeaderKeyMap) { key = rawHeaderKeyMap[key] || key; } setHeader(key, header); }); }, /** * Set the statusCode from the proxyResponse * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {proxyResponse} Res Response object from the proxy request * * @api private */ writeStatusCode: function writeStatusCode(req, res, proxyRes) { // From Node.js docs: response.writeHead(statusCode[, statusMessage][, headers]) if(proxyRes.statusMessage) { res.statusCode = proxyRes.statusCode; res.statusMessage = proxyRes.statusMessage; } else { res.statusCode = proxyRes.statusCode; } } }; var httpNative = require$$0__default$3['default'], httpsNative = require$$1__default$2['default'], web_o = webOutgoing, common$1 = common$3, followRedirects = followRedirects$1.exports; web_o = Object.keys(web_o).map(function(pass) { return web_o[pass]; }); var nativeAgents = { http: httpNative, https: httpsNative }; /*! * Array of passes. * * A `pass` is just a function that is executed on `req, res, options` * so that you can easily add new checks while still keeping the base * flexible. */ var webIncoming = { /** * Sets `content-length` to '0' if request is of DELETE type. * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {Object} Options Config object passed to the proxy * * @api private */ deleteLength: function deleteLength(req, res, options) { if((req.method === 'DELETE' || req.method === 'OPTIONS') && !req.headers['content-length']) { req.headers['content-length'] = '0'; delete req.headers['transfer-encoding']; } }, /** * Sets timeout in request socket if it was specified in options. * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {Object} Options Config object passed to the proxy * * @api private */ timeout: function timeout(req, res, options) { if(options.timeout) { req.socket.setTimeout(options.timeout); } }, /** * Sets `x-forwarded-*` headers if specified in config. * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {Object} Options Config object passed to the proxy * * @api private */ XHeaders: function XHeaders(req, res, options) { if(!options.xfwd) return; var encrypted = req.isSpdy || common$1.hasEncryptedConnection(req); var values = { for : req.connection.remoteAddress || req.socket.remoteAddress, port : common$1.getPort(req), proto: encrypted ? 'https' : 'http' }; ['for', 'port', 'proto'].forEach(function(header) { req.headers['x-forwarded-' + header] = (req.headers['x-forwarded-' + header] || '') + (req.headers['x-forwarded-' + header] ? ',' : '') + values[header]; }); req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers['host'] || ''; }, /** * Does the actual proxying. If `forward` is enabled fires up * a ForwardStream, same happens for ProxyStream. The request * just dies otherwise. * * @param {ClientRequest} Req Request object * @param {IncomingMessage} Res Response object * @param {Object} Options Config object passed to the proxy * * @api private */ stream: function stream(req, res, options, _, server, clb) { // And we begin! server.emit('start', req, res, options.target || options.forward); var agents = options.followRedirects ? followRedirects : nativeAgents; var http = agents.http; var https = agents.https; if(options.forward) { // If forward enable, so just pipe the request var forwardReq = (options.forward.protocol === 'https:' ? https : http).request( common$1.setupOutgoing(options.ssl || {}, options, req, 'forward') ); // error handler (e.g. ECONNRESET, ECONNREFUSED) // Handle errors on incoming request as well as it makes sense to var forwardError = createErrorHandler(forwardReq, options.forward); req.on('error', forwardError); forwardReq.on('error', forwardError); (options.buffer || req).pipe(forwardReq); if(!options.target) { return res.end(); } } // Request initalization var proxyReq = (options.target.protocol === 'https:' ? https : http).request( common$1.setupOutgoing(options.ssl || {}, options, req) ); // Enable developers to modify the proxyReq before headers are sent proxyReq.on('socket', function(socket) { if(server && !proxyReq.getHeader('expect')) { server.emit('proxyReq', proxyReq, req, res, options); } }); // allow outgoing socket to timeout so that we could // show an error page at the initial request if(options.proxyTimeout) { proxyReq.setTimeout(options.proxyTimeout, function() { proxyReq.abort(); }); } // Ensure we abort proxy if request is aborted req.on('aborted', function () { proxyReq.abort(); }); // handle errors in proxy and incoming request, just like for forward proxy var proxyError = createErrorHandler(proxyReq, options.target); req.on('error', proxyError); proxyReq.on('error', proxyError); function createErrorHandler(proxyReq, url) { return function proxyError(err) { if (req.socket.destroyed && err.code === 'ECONNRESET') { server.emit('econnreset', err, req, res, url); return proxyReq.abort(); } if (clb) { clb(err, req, res, url); } else { server.emit('error', err, req, res, url); } } } (options.buffer || req).pipe(proxyReq); proxyReq.on('response', function(proxyRes) { if(server) { server.emit('proxyRes', proxyRes, req, res); } if(!res.headersSent && !options.selfHandleResponse) { for(var i=0; i < web_o.length; i++) { if(web_o[i](req, res, proxyRes, options)) { break; } } } if (!res.finished) { // Allow us to listen when the proxy has completed proxyRes.on('end', function () { if (server) server.emit('end', req, res, proxyRes); }); // We pipe to the response unless its expected to be handled by the user if (!options.selfHandleResponse) proxyRes.pipe(res); } else { if (server) server.emit('end', req, res, proxyRes); } }); } }; var http = require$$0__default$3['default'], https = require$$1__default$2['default'], common = common$3; /*! * Array of passes. * * A `pass` is just a function that is executed on `req, socket, options` * so that you can easily add new checks while still keeping the base * flexible. */ /* * Websockets Passes * */ var wsIncoming = { /** * WebSocket requests must have the `GET` method and * the `upgrade:websocket` header * * @param {ClientRequest} Req Request object * @param {Socket} Websocket * * @api private */ checkMethodAndHeader : function checkMethodAndHeader(req, socket) { if (req.method !== 'GET' || !req.headers.upgrade) { socket.destroy(); return true; } if (req.headers.upgrade.toLowerCase() !== 'websocket') { socket.destroy(); return true; } }, /** * Sets `x-forwarded-*` headers if specified in config. * * @param {ClientRequest} Req Request object * @param {Socket} Websocket * @param {Object} Options Config object passed to the proxy * * @api private */ XHeaders : function XHeaders(req, socket, options) { if(!options.xfwd) return; var values = { for : req.connection.remoteAddress || req.socket.remoteAddress, port : common.getPort(req), proto: common.hasEncryptedConnection(req) ? 'wss' : 'ws' }; ['for', 'port', 'proto'].forEach(function(header) { req.headers['x-forwarded-' + header] = (req.headers['x-forwarded-' + header] || '') + (req.headers['x-forwarded-' + header] ? ',' : '') + values[header]; }); }, /** * Does the actual proxying. Make the request and upgrade it * send the Switching Protocols request and pipe the sockets. * * @param {ClientRequest} Req Request object * @param {Socket} Websocket * @param {Object} Options Config object passed to the proxy * * @api private */ stream : function stream(req, socket, options, head, server, clb) { var createHttpHeader = function(line, headers) { return Object.keys(headers).reduce(function (head, key) { var value = headers[key]; if (!Array.isArray(value)) { head.push(key + ': ' + value); return head; } for (var i = 0; i < value.length; i++) { head.push(key + ': ' + value[i]); } return head; }, [line]) .join('\r\n') + '\r\n\r\n'; }; common.setupSocket(socket); if (head && head.length) socket.unshift(head); var proxyReq = (common.isSSL.test(options.target.protocol) ? https : http).request( common.setupOutgoing(options.ssl || {}, options, req) ); // Enable developers to modify the proxyReq before headers are sent if (server) { server.emit('proxyReqWs', proxyReq, req, socket, options, head); } // Error Handler proxyReq.on('error', onOutgoingError); proxyReq.on('response', function (res) { // if upgrade event isn't going to happen, close the socket if (!res.upgrade) { socket.write(createHttpHeader('HTTP/' + res.httpVersion + ' ' + res.statusCode + ' ' + res.statusMessage, res.headers)); res.pipe(socket); } }); proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) { proxySocket.on('error', onOutgoingError); // Allow us to listen when the websocket has completed proxySocket.on('end', function () { server.emit('close', proxyRes, proxySocket, proxyHead); }); // The pipe below will end proxySocket if socket closes cleanly, but not // if it errors (eg, vanishes from the net and starts returning // EHOSTUNREACH). We need to do that explicitly. socket.on('error', function () { proxySocket.end(); }); common.setupSocket(proxySocket); if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead); // // Remark: Handle writing the headers to the socket when switching protocols // Also handles when a header is an array // socket.write(createHttpHeader('HTTP/1.1 101 Switching Protocols', proxyRes.headers)); proxySocket.pipe(socket).pipe(proxySocket); server.emit('open', proxySocket); server.emit('proxySocket', proxySocket); //DEPRECATED. }); return proxyReq.end(); // XXX: CHECK IF THIS IS THIS CORRECT function onOutgoingError(err) { if (clb) { clb(err, req, socket); } else { server.emit('error', err, req, socket); } socket.end(); } } }; (function (module) { var httpProxy = module.exports, extend = require$$1__default['default']._extend, parse_url = require$$0__default$2['default'].parse, EE3 = eventemitter3.exports, http = require$$0__default$3['default'], https = require$$1__default$2['default'], web = webIncoming, ws = wsIncoming; httpProxy.Server = ProxyServer; /** * Returns a function that creates the loader for * either `ws` or `web`'s passes. * * Examples: * * httpProxy.createRightProxy('ws') * // => [Function] * * @param {String} Type Either 'ws' or 'web' * * @return {Function} Loader Function that when called returns an iterator for the right passes * * @api private */ function createRightProxy(type) { return function(options) { return function(req, res /*, [head], [opts] */) { var passes = (type === 'ws') ? this.wsPasses : this.webPasses, args = [].slice.call(arguments), cntr = args.length - 1, head, cbl; /* optional args parse begin */ if(typeof args[cntr] === 'function') { cbl = args[cntr]; cntr--; } var requestOptions = options; if( !(args[cntr] instanceof Buffer) && args[cntr] !== res ) { //Copy global options requestOptions = extend({}, options); //Overwrite with request options extend(requestOptions, args[cntr]); cntr--; } if(args[cntr] instanceof Buffer) { head = args[cntr]; } /* optional args parse end */ ['target', 'forward'].forEach(function(e) { if (typeof requestOptions[e] === 'string') requestOptions[e] = parse_url(requestOptions[e]); }); if (!requestOptions.target && !requestOptions.forward) { return this.emit('error', new Error('Must provide a proper URL as target')); } for(var i=0; i < passes.length; i++) { /** * Call of passes functions * pass(req, res, options, head) * * In WebSockets case the `res` variable * refer to the connection socket * pass(req, socket, options, head) */ if(passes[i](req, res, requestOptions, head, this, cbl)) { // passes can return a truthy value to halt the loop break; } } }; }; } httpProxy.createRightProxy = createRightProxy; function ProxyServer(options) { EE3.call(this); options = options || {}; options.prependPath = options.prependPath === false ? false : true; this.web = this.proxyRequest = createRightProxy('web')(options); this.ws = this.proxyWebsocketRequest = createRightProxy('ws')(options); this.options = options; this.webPasses = Object.keys(web).map(function(pass) { return web[pass]; }); this.wsPasses = Object.keys(ws).map(function(pass) { return ws[pass]; }); this.on('error', this.onError, this); } require$$1__default['default'].inherits(ProxyServer, EE3); ProxyServer.prototype.onError = function (err) { // // Remark: Replicate node core behavior using EE3 // so we force people to handle their own errors // if(this.listeners('error').length === 1) { throw err; } }; ProxyServer.prototype.listen = function(port, hostname) { var self = this, closure = function(req, res) { self.web(req, res); }; this._server = this.options.ssl ? https.createServer(this.options.ssl, closure) : http.createServer(closure); if(this.options.ws) { this._server.on('upgrade', function(req, socket, head) { self.ws(req, socket, head); }); } this._server.listen(port, hostname); return this; }; ProxyServer.prototype.close = function(callback) { var self = this; if (this._server) { this._server.close(done); } // Wrap callback to nullify server after all open connections are closed. function done() { self._server = null; if (callback) { callback.apply(null, arguments); } }}; ProxyServer.prototype.before = function(type, passName, callback) { if (type !== 'ws' && type !== 'web') { throw new Error('type must be `web` or `ws`'); } var passes = (type === 'ws') ? this.wsPasses : this.webPasses, i = false; passes.forEach(function(v, idx) { if(v.name === passName) i = idx; }); if(i === false) throw new Error('No such pass'); passes.splice(i, 0, callback); }; ProxyServer.prototype.after = function(type, passName, callback) { if (type !== 'ws' && type !== 'web') { throw new Error('type must be `web` or `ws`'); } var passes = (type === 'ws') ? this.wsPasses : this.webPasses, i = false; passes.forEach(function(v, idx) { if(v.name === passName) i = idx; }); if(i === false) throw new Error('No such pass'); passes.splice(i++, 0, callback); }; }(httpProxy$3)); // Use explicit /index.js to help browserify negociation in require '/lib/http-proxy' (!) var ProxyServer = httpProxy$3.exports.Server; /** * Creates the proxy server. * * Examples: * * httpProxy.createProxyServer({ .. }, 8000) * // => '{ web: [Function], ws: [Function] ... }' * * @param {Object} Options Config object passed to the proxy * * @return {Object} Proxy Proxy object with handlers for `ws` and `web` requests * * @api public */ function createProxyServer(options) { /* * `options` is needed and it must have the following layout: * * { * target :





